Question

In an interview with John Hughes where he talks about Erlang and Haskell, he has the following to say about using stateful libraries in Erlang:

If I want to use a stateful library, I usually build a side effect-free interface on top of it so that I can then use it safely in the rest of my code.

What does he mean by this? I am trying to think of an example of how this would look, but my imagination and/or knowledge is failing me.

Was it helpful?

Solution

(I don't know Erlang, and I can't write Haskell, but I think I can answer nevertheless)

Well, in that interview the example of a random number generation library is given. Here is a possible stateful interface:

# create a new RNG
var rng = RNG(seed)

# every time we call the next(ceil) method, we get a new random number
print rng.next(10)
print rng.next(10)
print rng.next(10)

Output may be 5 2 7. To someone who likes immutability, this is plain wrong! It should be 5 5 5, because we called the method on the same object.

So what would a stateless interface be? We can view the sequence of random numbers as a lazily evaluated list, where next actually retrieves the head:

let rng = RNG(seed)
let n : rng = rng in
  print n
  let n : rng = rng in
    print n
    let n : rng in
      print n

With such an interface, we can always revert to a previous state. If two pieces of your code refer to the same RNG, they will actually get the same sequence of numbers. In a functional mindset, this is highly desirable.

Implementing this in a stateful language isn't that complicated. For example:

import scala.util.Random
import scala.collection.immutable.LinearSeq

class StatelessRNG (private val statefulRNG: Random, bound: Int) extends LinearSeq[Int] {
  private lazy val next = (statefulRNG.nextInt(bound), new StatelessRNG(statefulRNG, bound))

  // the rest is just there to satisfy the LinearSeq trait
  override def head = next._1
  override def tail = next._2
  override def isEmpty = false
  override def apply(i: Int): Int = throw new UnsupportedOperationException()
  override def length = throw new UnsupportedOperationException()
}

// print out three nums
val rng = new StatelessRNG(new Random(), 10)
rng.take(3) foreach (n => println(n))

Once you add a bit of syntactic sugar so that it feels like a list, this is actually quite nice.

OTHER TIPS

A key concept here is that of external mutable state. A library that has no external mutable state, is one that is free of side-effects. Every function in such a library only depends on the arguments passed into it.

  • If your function accesses any resource that wasn't created by it, given to it (ie. as a parameter), then it depends on external state.
  • If your function creates something that it doesn't pass back to the caller (and doesn't destroy it) then your function is creating external state.
  • When the external state from above can have different values at different times, then it is mutable.

Handy litmus tests that I use:

  • if function A needs to be run before function B then A creates external state that B depends on.
  • if a function I'm writing cannot be memoized, then it depends on external mutable state. (The memoization might not be a good idea because of memory pressure, but it should still be possible)
Licensed under: CC-BY-SA with attribution
scroll top