Eric Radman : a Journal

Local State and Environments

Lambda Expressions

OCaml and ML both use the operator fun or function to bind variables to an expression that is evaluated within it's own environment. My understanding is that in lisp this is done in what's known as a frame, and that collectively this linked list of frames constitutes the environment.

It's often said that Lambda expressions are simply anonymous functions, but this misses the important fact that every procedure has it's own local state. This is not true in Python, but in Ruby lambda eumulates this with some variation of the object Proc. The key here is to remember that let extends the environment it's used in. Each procedure has it's own copy of the environment it was created in, and therefore hidden local state.

Mutable References

The ability to create mutable references opens up some interesting methods in combination with the implicit closures of an ML function.

let makecounter increment =
  let c = ref 0 in
    fun () -> c := !c + increment; !c

So makecounter takes an int and returns a fun, which is created by binding it to a local environment within a frame.

# let c1 = makecounter 1;;
val c1 : unit -> int = <fun>
# c1 () ;;
- : int = 1
# c1 () ;;
- : int = 2
# let c2 = makecounter 10;;
val c2 : unit -> int = <fun>
# c2 () ;;
- : int = 10
# c2 () ;;
- : int = 20

Here c1 and c2 both inherit from the environment they're created in, but they each have their own state.

No Subsequent Influence

Another way to demonstrate that every procedure is a closed system is to re-assign an unbound variable and see if it's still in scope.

# let x = 4 ;;

First note that let is immutable, and that the eager evaluation of OCaml is unambiguos.

# let f = x * 2;;
val f : int = 8

let didn't create a function named f, it bound an evalutated expression to the symbol called f. So we need to create a procedure to use.

#  let f = fun () -> x * 2;;
val f : unit -> int = <fun>
# f () ;;
- : int = 8
# let x = 5 ;;
val x : int = 5
# f () ;;
- : int = 8