Tag Archives: ontology

Notes on “the digital”

It is mathematically demonstrable that the ontology of set theory and the ontology of the digital are not equivalent.

The realm of the digital is that of the denumerable: to every finite stream of digits corresponds a single natural number, a finite ordinal. If we set an upper bound on the length of a stream of digits – let’s say, it has to fit into the available physical universe, using the most physically compact encoding available – then we can imagine a “library of Boole”, finite but Vast, that would encompass the entirety of what can be digitally inscribed and processed. Even larger than the library of Boole is the “digital universe” of sequences of digits, D, which is what we get if we don’t impose this upper bound. Although infinite, D is a single set, and is isomorphic to the set of natural numbers, N. It contains all possible digital encodings of data and all possible digital encodings of programs which can operate on this data (although a digital sequence is not intrinsically either program or data).

The von Neumann universe of sets, V, is generated out of a fundamental operation – taking the powerset – applied recursively to the empty set. It has its genesis in the operation which takes 0, or the empty set {}, to 1, or the singleton set containing the empty set, {{}}, but what flourishes out of this genesis cannot in general be reduced to the universe of sequences of 0s and 1s. The von Neumann universe of sets is not coextensive with D but immeasurably exceeds it, containing sets that cannot be named or generated by any digital procedure whatsoever. V is in fact too large to be a set, being rather a proper class of sets.

Suppose we restrict ourselves to the “constructible universe” of sets, L, in which each level of the hierarchy is restricted so that it contains only those sets which are specifiable using the resources of the hierarchy below it. The axiom of constructibility proposes that V=L – that no set exists which is not nameable. This makes for a less extravagantly huge universe; but L is still a proper class. D appears within L as a single set among an immeasurable (if comparatively well-behaved) proliferation of sets.

A set-theoretic ontology such as Badiou’s, which effectively takes the von Neumann universe as its playground, is thus not a digital ontology. Badiou is a “maximalist” when it comes to mathematical ontology: he’s comfortable with the existence of non-constructible sets (hence, he does not accept the “axiom of constructibility”, which proposes that V=L), and the limitations of physical or theoretical computability are without interest for him. Indeed, it has been Badiou’s argument (in Number and Numbers) that the digital or numerical enframing of society and culture can only be thought from the perspective of a mathematical ontology capacious enough to think “Number” over and above the domain of “numbers”. This is precisely the opposite approach to that which seeks refuge from the swarming immensity of mathematical figures in the impenetrable, indivisible density of the analog.

What is the ontology of code?

If, as is sometimes said, software is eating the world, absorbing all of the contents of our lives in a new digital enframing, then it is important to know what the logic of the software-digested world might be – particularly if we wish to contest that enframing, to try to wriggle our way out of the belly of the whale. Is it perhaps object-oriented? The short answer is “no”, and the longer answer is that the ontology of software, while it certainly contains and produces units and “unit operations” (to borrow a phrase of Ian Bogost’s), has a far more complex topology than the “object” metaphor suggests. One important thing that practised software developers mostly understand in a way that non-developers mostly don’t is the importance of scope; and a scope is not an object so much as a focalisation.

Cover of "Object-Oriented Modeling and Design with UML"

The logic of scope is succinctly captured by the untyped lambda calculus, which is one of the ways in which people who really think about computation think about computation. Here’s a simple example. Suppose, to begin with, we have a function that takes a value x, and returns x. We write this as a term in the lambda calculus as follows:

\lambda x.x

The \lambda symbol means: “bind your input to the variable named on the left-hand side of the dot, and return the value of the term on the right-hand side of the dot”. So the above expression binds its input to the variable named x, and returns the value of the term “x”. As it happens, the value of the term “x” is simply the value bound to the variable named x in the context in which the term is being evaluated. So, the above “lambda expression” creates a context in which the variable named x is bound to its input, and evaluates “x” in that context.

We can “apply” this function – that is, give it an input it can consume – just by placing that input to the right of it, like so:

(\lambda x.x)\;5

This, unsurprisingly, evaluates to 5.

Now let’s try a more complex function, one which adds two numbers together:

\lambda x.\lambda y.x+y

There are two lambda expressions here, which we’ll call the “outer” and “inner” expressions. The outer expression means: bind your input to the variable named x, and return the value of the term “\lambda y.x+y ”, which is the inner expression. The inner expression then means: bind your input to the variable named y, and return the value of the term “x+y”.

The important thing to understand here is that the inner expression is evaluated in the context created by the outer expression, a context in which x is bound, and that the right-hand side of the inner expression is evaluated in a context created within this first context – a new context-in-a-context, in which x was already bound, and now y is also bound. Variable bindings that occur in “outer” contexts, are said to be visible in “inner” contexts. See what happens if we apply the whole expression to an input:

(\lambda x.\lambda y.x+y)\;5 = \lambda y.5+y

We get back a new lambda expression, with 5 substituted for x. This expression will add 5 to any number supplied to it. So what if we want to supply both inputs, and get x+y?

\begin{array} {lcl}((\lambda x.\lambda y.x+y)\;5)\;4 & = & (\lambda y.5+y)\;4 \\ & = & 5 + 4 \\ & = & 9\end{array}

Some simplification rules in the lambda calculus notation allow us to do away with both the nested parentheses and the nested lambda expressions, so that the above can be more simply written as:

\lambda xy.x+y\;5\;4 = 9

There is not much more to the (untyped) lambda calculus than this. It is Turing-complete, which means that any computable function can be written as a term in it. It contains no objects, no structured data-types, no operations that change the state of anything, and hence no implicit model of the world as made up of discrete pieces that respond as encapsulated blobs of state and behaviour. But it captures something significant about the character of computation, which is that binding is a fundamental operation. A context is a focus of computation in which names and values are bound together; and contexts beget contexts, closer and richer focalisations.

So far we have considered only the hierarchical nesting of contexts, which doesn’t really make for a very exciting or interesting topology. Another fundamental operation, however, is the treatment of an expression bound in one context as a value to be used in another. Contexts migrate. Consider this lambda expression:

\lambda f.f\;4

The term on the right-hand side is an application, which means that the value bound to f must itself be a lambda expression. Let’s apply it to a suitable expression:

\begin{array} {lcl}(\lambda f.f\;4) (\lambda x.x*x) & = & (\lambda x.x*x)\;4 \\ & = & 4*4 \\ & = & 16\end{array}

We “pass” a function that multiplies a number by itself, to a function that applies the function given to it to the number 4, and get 16. Now let’s make the input to our first function be a function constructed by another function, that binds one of its variables and leaves the other “free” – a “closure” that “closes over” its context, whilst remaining partially open to new input:

\begin{array} {lcl}(\lambda f.f\;4) ((\lambda x.\lambda y.x*y)\;5) & = & (\lambda f.f\;4) (\lambda y.5*y) \\ & = & (\lambda y.5*y) 4 \\ & = & 5* 4 \\ & = & 20\end{array}

If you can follow that, you already understand lexical scoping and closures better than some Java programmers.

My point here is not that the untyped lambda calculus expresses the One True Ontology of computation – it is equivalent to Turing’s machine-model, but not in any sense more fundamental than it. “Functional” programming, a style which favours closures and pure functions over objects and mutable state, is currently enjoying a resurgence, and even Java programmers have “lambdas” in their language nowadays; but that’s not entirely the point either. The point I want to make is that even the most object-y Object-Oriented Programming involves a lot of binding (of constructor arguments to private fields, for example), and a lot of shunting of values in and out of different scopes. Often the major (and most tedious) effort involved in making a change to a complex system is in “plumbing” values that are known to one scope through to another scope, passing them up and down the call stack until they reach the place where they’re needed. Complex pieces of software infrastructure exist whose entire purpose is to enable things operating in different contexts to share information with each other without having to become tangled up together into the same context. One of the most important questions a programmer has to know how to find the answer to when looking at any part of a program is, “what can I see from here?” (and: “what can see me?”).

Any purported ontology of computation that doesn’t treat as fundamental the fact that objects (or data of any kind) don’t just float around in a big flat undifferentiated space, but are always placed in a complex landscape of interleaving, interpenetrating scopes, is missing an entire dimension of structure that is, I would argue, at least as important as the structure expressed through classes or APIs. There is a perspective from which an object is just a big heavy bundle of closures, a monad (in the Leibnitzian rather than category-theoretical sense) formed out of folds; and from within that perspective you can see that there exist other things which are not objects at all, or not at all in the same sense. (I know there are languages which model closures as “function objects”, and shame on you).

It doesn’t suit the narrative of a certain attempted politicisation of software, which crudely maps “objects” onto the abstract units specified by the commodity form, to consider how the pattern-thinking of software developers actually works, because that thinking departs very quickly from the “type-of-thing X in the business domain maps to class X in my class hierarchy” model as soon as a system becomes anything other than a glorified inventory. Perhaps my real point is that capitalism isn’t that simple either. If you want a sense of where both capitalism and software are going, you would perhaps do better to start by studying the LMAX Disruptor, or the OCaml of Jane Street Capital.