title | description | canonical |
---|---|---|
Let Binding | Let binding syntax for binding to values in ReScript | /docs/manual/v11.0.0/let-binding |
A "let binding", in other languages, might be called a "variable declaration". let
binds values to names. They can be seen and referenced by code that comes after them.
<CodeTab labels={["ReScript", "JS Output"]}>
letgreeting="hello!"letscore=10letnewScore=10+score
vargreeting="hello!";varscore=10;varnewScore=20;
Bindings can be scoped through {}
.
<CodeTab labels={["ReScript", "JS Output"]}>
letmessage= { letpart1="hello"letpart2="world"part1++" "++part2 } // `part1` and `part2` not accessible here!
varmessage="hello world";
The value of the last line of a scope is implicitly returned.
ReScript's if
, while
and functions all use the same block scoping mechanism. The code below works not because of some special "if scope"; but simply because it's the same scope syntax and feature you just saw:
<CodeTab labels={["ReScript", "JS Output"]}>
ifdisplayGreeting { letmessage="Enjoying the docs so far?"Console.log(message) } // `message` not accessible here!
if(displayGreeting){console.log("Enjoying the docs so far?");}
Let bindings are "immutable", aka "cannot change". This helps our type system deduce and optimize much more than other languages (and in turn, help you more).
The above restriction might sound unpractical at first. How would you change a value then? Usually, 2 ways:
The first is to realize that many times, what you want isn't to mutate a variable's value. For example, this JavaScript pattern:
varresult=0;result=calculate(result);result=calculateSomeMore(result);
...is really just to comment on intermediate steps. You didn't need to mutate result
at all! You could have just written this JS:
varresult1=0;varresult2=calculate(result1);varresult3=calculateSomeMore(result2);
In ReScript, this obviously works too:
<CodeTab labels={["ReScript", "JS Output"]}>
letresult1=0letresult2=calculate(result1) letresult3=calculateSomeMore(result2)
varresult1=0;varresult2=calculate(0);varresult3=calculateSomeMore(result2);
Additionally, reusing the same let binding name overshadows the previous bindings with the same name. So you can write this too:
<CodeTab labels={["ReScript", "JS Output"]}>
letresult=0letresult=calculate(result) letresult=calculateSomeMore(result)
varresult=calculate(0);varresult$1=calculateSomeMore(result);
(Though for the sake of clarity, we don't recommend this).
As a matter of fact, even this is valid code:
<CodeTab labels={["ReScript", "JS Output"]}>
letresult="hello"Console.log(result) // prints "hello"letresult=1Console.log(result) // prints 1
varresult=1;console.log("hello");console.log(1);
The binding you refer to is whatever's the closest upward. No mutation here! If you need real mutation, e.g. passing a value around, have it modified by many pieces of code, we provide a slightly heavier mutation feature.
Private let bindings are introduced in the release 7.2.
In the module system, everything is public by default, the only way to hide some values is by providing a separate signature to list public fields and their types:
moduleA: { letb: int } = { leta=3letb=4 }
%%private
gives you an option to mark private fields directly
moduleA= { %%private(leta=3) letb=4 }
%%private
also applies to file level modules, so in some cases, users do not need to provide a separate interface file just to hide some particular values.
Note interface files are still recommended as a general best practice since they give you better separate compilation units and also they're better for documentation.
Still, %%private
is useful in the following scenarios:
Code generators. Some code generators want to hide some values but it is sometimes very hard or time consuming for code generators to synthesize the types for public fields.
Quick prototyping. During prototyping, we still want to hide some values, but the interface file is not stable yet.
%%private
provides you such convenience.