In the previous post, we discussed the expressions in OCaml and used the toplevel as a calculator. It is fun but for any serious programs we need variables and functions, which we will talk about in this and next posts. But wait a second. We said that functional programming avoids state and mutable data but variables sound like mutable things, right? Yes, it is true in imperative programming as a variable basically holds program state and refers to the machine representation of data structures and its value changes over the time. However, the idea of a variable in functional programming is different and the purpose is to designate values symbolically. It also lets us factor certain computations by naming a computed value so that it can be reused later. That is, variables are simply name binding to values.
In OCaml, variables can be introduced by the
let keyword, called
let binding. The name of a variable, i.e. identifier, has to begin with a lowercase letter and consist of letters, digits, the underscore
_ or the prime
# let x = 2 + 3;; val x : int = 5
As shown above, the toplevel prints the variable
x's value and more importantly its type
int. Although this is a very simple example, OCaml can infer the types of very complicated expressions by the unification algorithm, which is the process of finding a substitution that makes two given terms equal. Pattern matching, a very power construct in OCaml, is actually also based on the unification algorithm. Type inference is very cool because it enables us to write succinct code like dynamic languages yet type-safe. Of course, we are still allowed to declare the type of a variable explicitly.
# let x : int = 2 + 3;; val x : int = 5
It is usually not necessary but could be needed in rare situations where OCaml compiler cannot determine the type of an expression. Besides, it can also be useful in case of subtyping.
Every variable binding has a scope. In a toplevel, the scope of
let binding is everything that follows it in the session. When in a source file (or module), the scope is the remainder of that file/module.
Local binding, i.e. a variable binding whose scope is limited to an expression, is also possible (and encouraged) with the syntax
let <variable> = <expression1> in <expression2>
The <expression2> is called the body of the
let in construct. The <variable> is defined only in the body <expression2> but not in <expression1>. It is an expression and the value is the value of the body. For example,
# let z = let x = 1 in let y = 2 in x + y;; val z : int = 3
let in bindings do not have global scope. After evaluation, the variable
x is the one defined earlier.
# x;; - : int = 5
That is, a
let binding in an inner scope can shadow the definition from an outer scope. Binding is not an assignment and it is static (lexical scoping), i.e. the value associated with a variable is determined by nearest enclosing definition.
In the next post, we will discuss the most exciting thing in OCaml, functions!