Functional Friday - Episode 2
Learning Rescript - Expressions
Welcome back to Functional Friday, the place where functions stopped calling each other, because they had constant arguments 🥁! In the last episode we had a look on how let bindings and types work in ReScript and how they compare to JavaScript/TypeScript.
In this episode will focus mostly on expressions and we're going to use them to start introducing some differences between functional programming languages and imperative ones.
2.1 Expressions, uh? 🤔
Normally speaking, an expression is
the action of making known someone's thoughts or feelings, whereas in programming an expression is an entity that may require
further computation in order to determine its value.
Also,
in functional programming expressions are considered the basic
building block
for our programs, while imperative languages relies more on
statements (or
commands).
It's important to fully understand this concept because, as we'll see, ReScript syntax is built around this.
2.2 Let Expressions
Previously, we saw how to use a
let
binding, and we noticed how
similar was to declaring and assigning a variable in an imperative
language.
let areYouHappy = true
However, up to now we just talked about
let
definition. There's another
usage of let
which is an
expression
let nextYear = currentYear + 1
At a first look, it may seems there're no huge difference, but there's
an important thing to notice:
nextYear
value is not
immediately known and it depends on
currentYear
value.
Being used to read left to right here can make things
counter-intuitive, so let's try to read right to left instead:
"considering the value
1
, add the value of
currentYear
and
bind it to
nextYear
".
This expresses (pun not intended) quite well what ReScript is going to
do in order to determine
nextYear
value.
It is probably worth mentioning that ReScript will not allow us to write expressions that rely on coercion to be evaluated; things like
const accountBalance = 2 + "1.00"
will result in a type error since they violate inference rules [1].
As we're going to see in incoming episodes,
let
expressions are a really
powerful tool. Just to give you an idea, named functions themselves
are let
expressions, i.e.
let isApophisHittingUs = (currentYear) =>
if currentYear == 2029 { "Oh dayum" }
else { "Phew! Not today" }
I will not go into a deep explanation about it for two main reasons:
- if you are familiar with JavaScript/TypeScript, it will probably remind you about arrow functions and ternary operator, so you can probably have a good guessing about what it does;
- we will talk in-depth about functions in a dedicated episode;
Nonetheless, keep in mind this example since it will lead us straight to the next topic.
2.3 If Expressions
If we put enough attention reading through the last lines of code, we may have probably noticed this
if currentYear == 2029 { "Oh dayum" }
else { "Phew! Not today" }
which reads as follows: the expression
if exp_1 { exp_2 } else { exp_3 }
evaluates to exp_2
if
exp_1
evaluates to
true
, otherwise it evaluates to
exp_3
. In functional programming
exp_1
is generally referred as
the if
expression
guard.
While in imperative languages
if-else
are statements, in
ReScript (and in OCaml too, for the matter) they're expressions and as
such they can be used and combined with other expressions, such the
let expressions we just talked about:
let drink = if isMorning { "Coffee" } else { "Tea" }
which, as said previously, is similar to writing a ternary operator in JavaScript/TypeScript
const drink = isMorning ? "Coffee" : "Tea";
If
expressions can be nested and
we can use as well a ternary syntactic sugar, so writing
// Nested if-else
let drink =
if isMorning { "Coffee " }
else {
if isLunchTime { "Water" }
else { "Tea" }
}
// Ternary like
let drink = isMorning ? "Coffee" : "Tea"
works just fine as well[2]. Aside from aforementioned similarities, there are few important aspects and several differences compared to JavaScript/TypeScript to keep in mind. Let's check them out.
1. Guard type should be a bool
Coming from JavaScript/TypeScript, we may be tempted to write something like this:
let drink = if 1 { "Coffee" } else { "Tea" }
but in ReScript this will result into an error
This has type: int
Somewhere wanted: bool
This happens because ReScript strictly requires the guard type to be
a bool
, so no truthy/falsy
values are allowed.
2. If and else branches should return expressions of the same type
In JavaScript/TypeScript, we may be used to write ternary like the following one
const price = isFormattedAsString ? "0.00" : 0.0;
but in ReScript, this will result into an error
This has type: int
Somewhere wanted: string
You can convert int to string with Belt.Int.toString.
in such circumstances the compiler will also provide us a way to fix our expression, which is really helpful.
The reason of this rule lies in the fact that we need to give this
expression an overall type, but because we're statically
type-checking, we don't know which branch will be executed at
runtime. Therefore both branches must return the same type
t
, which is the type of the
expression [3]. Also notice how the type
expectation comes from the
if
branch, and it's because
ReScript evaluates the
if
branch first.
3. Braces and implicit returns
As you may have noticed, we're expected to always wrap
if-else
expressions with braces (with the ternary syntactic sugar being an
exception). This is called block scoping and the value in the last
line in each scope is always implicitly returned. Block scoping can
be used in several ways, including let expressions
let fullName = {
let firstName = "Thomas"
let lastName = "Anderson"
`${firstName} A. ${lastName}` // this line is implicitly returned
}
4. Else branch is mandatory
As obvious as it may sound, we always need to define an
else
branch, otherwise we're
going to face an interesting type error message. The following code
let dinner = if amIHungry { "Pizza" }
This has type: string
Somewhere wanted: unit
the unit
type is a special
type that has a single value
()
and it compiles to
undefined
. While there are
some specific cases where it may be useful or even beneficial to
intentionally omit the
else
branch, for now we'll
keep it simple and just consider it mandatory.
2.4 Let's wrap it up! 🫡
In this episode we saw what expressions are, how to use both
let
and
if
expressions, how they compare
to JavaScript/TypeScript and how the type system interacts, being way
more strict than what we're used to. We now have some very basics
building blocks and I encourage you to jump into the
playground
and experiment yourself. Again, these arguments will recur in the
future so keeping this article in mind may be useful.
As always, thank you for reading through the whole article: I really
hope you enjoyed the content and you'll stick around for the
next episode. Up to then, happy coding and... see you next time!
Cheers! 🤓
Comments
Post a Comment