I saw below code:
let () = assert (f input1 = output1)
The assert
expression returns a value of type unit
, which has only one possible value displayed as ()
.
My understanding is, the let
definition should bind a name/identifier
to an expression, but ()
is a value here.
As I tried in utop:
utop # let () = assert (1 = 1);; <==== nothing output, not sure what's going on behind the scene.
Then I replaced the ()
to a name x
:
utop # let x = assert (1 = 1);;
val x : unit = () <========= This looks as expected.
How could we use a value instead of a name/identifier in let
expression?
Or put another way, how could we bind anything to a value?
According to this part of the OCaml language specification.
The
let
andlet rec
constructs bind value names locally. The constructlet pattern1 = expr1 and … and patternn = exprn in expr
evaluates expr1 … exprn in some unspecified order and matches their values against the patterns pattern1 … patternn. If the matchings succeed, expr is evaluated in the environment enriched by the bindings performed during matching, and the value of expr is returned as the value of the whole let expression. If one of the matchings fails, the exception Match_failure is raised.
And from here:
Variable patterns
A pattern that consists in a value name matches any value, binding the name to the value.
Constant patterns
A pattern consisting in a constant matches the values that are equal to this constant.
So in my case, the ()
on the left side of the =
is a constant pattern, which matches exactly to the value ()
. But there's no identifiers to bind to in the pattern string ()
. So no binding is happening.
I made a detailed explanation.
The semantics of let <pat> = <expr>
in OCaml is to evaluate <expr>
first and then apply the resulting value to the pattern on the left, binding any free variables in <pat>
. If there are no variables at all, then nothing is bound so <expr>
was evaluated for its side effect. E.g., here are some intentionally absurd examples,
let 0 = 0
Here <expr>
is the value 0
and <pat>
is a constant 0
. OCaml will evaluate 0
, which evaluates to itself, and apply this value to the 0
constant, which results in a match. But what if we write,
let 0 = 1
In this case, OCaml will raise an exception Match_failure
as 0
doesn't structurally match with 1
.
Furthermore, you can find that matching is everywhere in OCaml, not only on the left side of let. Everywhere where a variable is bound to a value, you can use a more complex pattern instead.
You can see OCaml as a machine that evaluates expressions and then matches them against patterns, the process called deconstruction. So an OCaml program, is a series of evaluations and deconstructions. And usually it ends up in a deconstructing the final value to a ()
pattern.