smwikipedia
smwikipedia

Reputation: 64423

How could we use a value instead of a name/identifier in let expression?

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?

ADD 1 - 9:51 AM 1/9/2025

According to this part of the OCaml language specification.

The let and let rec constructs bind value names locally. The construct

let 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.

Upvotes: 0

Views: 72

Answers (3)

Chris
Chris

Reputation: 36660

As an addendum to octachron's answer:

let both Either.(Left x | Right x) = x
let first_list_element_or_empty ([] as x | x :: _ ) = x

This use of or-patterns only works if all patterns (separated by |) bind the same names, and each binding has the same type.

Values bound to _ can be of any type and do not impact this.

Upvotes: 1

ivg
ivg

Reputation: 35280

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.

Upvotes: 2

octachron
octachron

Reputation: 18912

In the definition

let () = ...

() is not a value but a pattern. More precisely, this is the pattern which matches the value () and doesn't bind any variable.

This is the same mechanism which allow to write

let (x,y) = (1, 2)

or equivalently

let f (x,y) = x + y

or

let f p = match p with (x,y) -> x + y

Where (x,y) is the pattern that matches a tuple and binds its first argument to x and its second to y.

Note that any pattern is allowed in the left-hand side of let definition or as function argument

let empty [] = 0

is valid for instance, even if the compiler emits a warning

Warning 8 [partial-match]: this pattern-matching is not exhaustive. Here is an example of a case that is not matched: ::

However, using partial pattern in this way will raise an Match_failure exception when applied to a value that doesn't match the partial pattern:

empty [1];;

Exception: Match_failure ("//toplevel//", ...).

Thus it is generally better to use only irrefutable patterns in let definitions or in function arguments. This is mostly useful with tuples, records, and (), but not always. For instance the following functions are total:

let both Either.(Left x | Right x) = x
let first_list_element_or_empty ([] as x | x :: _ ) = x

Upvotes: 2

Related Questions