Lexical Scoping in OCaml

One turtle on top of another.

Like many other modern languages, OCaml uses lexical (or static) scoping.  That is, in OCaml, when your function includes a name that calls a variable, in the function, that variable has the value when the function is defined.  The opposite is dynamic scoping, in which the variable has the value when the function is called.  E.g.,

let a = 1 ;;      (* OCaml reads this as 'from now on until otherwise specified, a = 1.' *)  
let f x = x + a ;;      (* f x = x + a can be read as f x = x + 1 because of OCaml's lexical scope. *) 
let a = 2 ;;      (* The old value of a is shadowed, but not overwritten. a = 2 from now on. *)

What would OCaml return when you call f 2?  Because at the time when f is defined, a = 1, so, OCaml sees f‘s definition as f x = x + 1.  Therefore, it returns

# f 2 ;;
- : int = 3

Note that the result would be different for a language with dynamic scoping.  With dynamic scoping, because a = 2 when the function is called, f 2 would return 4 instead.

What happens if we now define f again the same way?

# let f x = x + a ;;      (* OCaml reads this definition as f x = x + 2, because a = 2 now. *)
val f : int -> int = <fun>
# f 2 ;;  (* As expected, f 2 returns 2 + 2. *)
- : int = 4

The new definition of f shadows the old definition, and so OCaml returns 4 instead of 3 in this case.  Of course, the variable in question can be a function too!  E.g.,

let a = 1 ;;
let f x = x + a ;;
let a = 100 ;;
let g x = 2 * f x ;;      (* OCaml reads this as g x = 2 * (x + 1) *)

# g 2 ;;      (* g 2 = 2 * (2 + 1) *)
- : int = 6      

let f x = x + a ;;      (* OCaml reads this as f x = x + 100.  The new definition of f shadows the old one. *)    

# g 2 ;;      (* When g was defined, f x = x + 1, so the new definition of f does not change the value of g 2. *)
- : int = 6

One have to distinguish between an input and a variable called in a function though.  An input is passes on to the function at the time the function is called.  That is, the latest value of the input is passed.  E.g.,

let a = 100 ;;
let f x = x + a ;;
# let h f x = 2 * (f x) ;;      (* h takes f and x as inputs. *)
val h : ('a -> int) -> 'a -> int = <fun>
# h f 2 ;;      (* Because f x = x + 100, h f 2 = 2 * (2 + 100) = 204. *)
- : int = 204
let f x = a * x ;;      (* This new definition of f shadows the old one. *) 
# h f 2 ;;      (*  Because f x = a * x now, h f 2 = 2 * (100 * 2) = 400. *)
- : int = 400

One advantage of lexical scope is that values can be determined at compile time.  This is why it is also known as early binding.  With dynamic scoping, values can in general only be determined at run time, and thus is known as late binding.

Closure is a closely related concept in a language with first-class functions, like OCaml!  More later!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.