Sunday 10 June 2012

Unthreading

Multi-threading is hard. To fully understand a piece of multi-threaded code the reader must mentally interleave two or more competing timelines. Our poor primate brains have enough trouble managing one thread of execution.

But what if we didn't have to carry around the idea of time at all? What if we could move about the codebase without having to mentally pause, play, fast-forward and rewind a hypothetical execution instance of the program?

The venerable and venerated Structure and Interpretation of Computer Programs comments with sadness on the entrance of the mutation-snake into the Lisp Garden of Eden: 
With objects, we must be concerned with how a computational object can change and yet maintain its identity. This will force us to abandon our old substitution model of computation in favor of a more mechanistic but less theoretically tractable environment model of computation. The difficulties of dealing with objects, change, and identity are a fundamental consequence of the need to grapple with time in our computational models. These difficulties become even greater when we allow the possibility of concurrent execution of programs.
Immutability makes code easier to understand because it allows the reader to use a simpler mental model.

Code that exclusively employs immutable (immortal) variables is effectively unthreaded from the reader's point of view. The meaning of the code is no longer dependent on time at all. The value of a symbol is a function of its definition - history is irrelevant.

Single-threaded code is easier to understand than multi-threaded code. But unthreaded code is best of all.

2 comments:

  1. Good point. Reading input isn't (normally) considered mutability, though it is referentially non-transparent and does force the reader to consider time.

    There's definitely a deep relationship between the two concepts. Perhaps you could consider immutability to be a special case of referential transparency for constant functions?

    ReplyDelete
  2. I mean a value is basically a function that takes no arguments. In Lisp, they're syntactically distinct because you need () for function invocation, but in Haskell there's really no difference. So I meant that an immutable value is indistinguishable from a function that takes no arguments and always returns the same answer.

    It's a good point about the I/O monad. My understanding of Haskell I/O is that it models I/O as a function from one state of the world to another, and that the world is threaded through I/O actions by the monad.

    In the end, Haskell I/O functions are referentially transparent because they don't do I/O - they just describe it. The non-referentially transparent stuff is delegated to the evaluation environment that invokes the main function.

    ReplyDelete