Daniel's Blog

Clojure and Data Driven Programming

Clojure is a multi-paradigm programming language that encourages Data Driven Programming (DDP). Let's start by taking apart "Data Driven Programming" as a term into individual words and see what it means.

  1. Data : Facts and statistics collected together for reference or analysis.
  2. Driven : Motivated or determined by a specified factor or feeling.
  3. Programming : The process or activity of writing computer programs.

One can interpret "Data Driven Programming" to be writing computer programs that are motivated by transforming collected facts, which is different from programming paradigms such as functional programming (FP) or object oriented programming (OOP), although they are not orthogonal to DDP. Clojure, however, may not be well received among FP enthusiasts since it is a dynamically typed programming language whereas in FP people prefer to invest time in making sure the relationships between functions are sound using advanced type systems. Data, or rather, values, are secondary to the type and structure of data.

At this point, there will be people clamouring for the recognition that advanced type systems also ensure safety and correctness of the program and thus the data transformation process as well. There is nothing DDP does that typed FP cannot do.

In my view, every turing-complete programming language can do whatever other languages in the same category of turing-completeness can do, the difference is in the how, not the what.

How Clojure encourages Data Driven Programming.

Let's start with a interesting property of the basic data structure abstraction in Clojure, sequences. All data structures in clojure support sequencing. You can read more here: https://clojure.org/reference/data_structures

Sequences are monads.

A monad is defined by the monadic law, and in particular the third law of Associativity. In simpler terms, you need to provide a few things.

  1. a way to map over values.
  2. a way to construct a monad M(x) for any given value x.
  3. a way to join/flatten M(M(x)) to M(x).

To put it in the context of clojure,

  1. map
  2. seq
  3. mapcat

Monads are nice in that it provides a consistent API for the given construct, which is why it's essential for some languages, especially typed FP languages to provide a simpler way to manage code. However, the problem with monads is that they don't compose in a simple way, thus monad transformers which generalises monad operations for stacked monads are used.

How about Clojure? How do Clojure code deal with nested vectors and maps?

Since collections also implement the same shared set of interfaces, in particular, the Iterable and Collection, among other things, we can derive a second interesting property of Clojure collections.

Clojure collections are also monad transformers.

In particular, the into function serves the same operation as lift in monad transformers when you need to convert between collections.

(into [] {:a 1}) ;=> <a href="/a-1">:a 1</a>

(into {} <a href="/a-1">:a 1</a>) ;=> {:a 1}

What?

Clojure goes beyond just providing data structures that have a consistent API with simple ways to operate between each other, all data structures can be used as functions that accepts a key as input to return a value, and clojure also engages in nil-punning so operations treat nil differently in a different contexts, among other things.

All of this ensures that in Clojure, modelling your program as transformations of facts is the path of least resistance. Instead of creating complicated types and functions to mangle type relationships, you operate on data as-is using the restricted set of data structures that already provide the operations you need to do so.

This also meant that Clojure reduces the drive for complicated macros and DSLs in our software, because the primary driver of how we structure logic is how data is ingested than how functions are written. For example, to pull data from a database, instead of writing a macro or functions that handles how entities relate to each other like ORMs, we simply use data structures in Clojure that then gets transformed to SQL.


Recent posts