ReasonML: basic values and types

[2017-12-08] dev, reasonml
(Ad, please don’t block)

Table of contents for this series of posts: “What is ReasonML?


In this blog post, we’ll look at ReasonML’s support for booleans, integers, floats, strings, characters and the unit type. We’ll also see a few operators in action.

To explore, we’ll use the interactive ReasonML command line rtop, which is part of the package reason-cli (the manual explains how to install it).

Interactions in rtop  

Interactions in rtop look as follows.

# !true;
- : bool = false

Two observations:

  • You must end the expression !true; with a semicolon in order for it to be evaluated.
  • rtop always prints out the types of the results it computes. That is especially helpful later on, with more complicated types, e.g. the types of functions.

ReasonML is statically typed – what does that mean?  

Values in ReasonML are statically typed. What does static typing mean?

On one hand, we have the term type. In this context, type means “set of values”. For example bool is the name of the type of all boolean values: the (mathematical) set {false, true}.

On the other hand, we make the following distinction in the context of the life cycle of code:

  • Static: at compile time, without running the code.
  • Dynamic: at runtime, while the code is running.

Therefore static typing means: ReasonML knows the types of values at compile time. And types are also known while editing code, which supports intelligent editing features.

Get used to working with types  

We have already encountered one benefit of static typing: editing support. It also helps with detecting some kinds of errors. And it often helps with documenting how code works (in a manner that is automatically checked for consistency).

In order to reap these benefits, you should get used to working with types. You get help in two ways:

  • ReasonML often infers types (writes them for you). That is, a passive knowledge of types gets you surprisingly far.
  • ReasonML gives descriptive error messages when something goes wrong that may even include tips for fixing the problem. That is, you can use trial and error to learn types.

No ad hoc polymorphism (yet)  

Ad hoc polymorphism may sound brainy, but it has a simple definition and visible practical consequences for ReasonML. So bear with me.

ReasonML does not currently support ad hoc polymorphism where the same operation is implemented differently depending on the types of the parameters. Haskell, another functional programming language supports ad hoc polymorphism via type classes. ReasonML may eventually support it via the similar modular implicits.

ReasonML not supporting ad hoc polymorphism means that most operators such as + (int addition), +. (float addition) and ++ (string concatenation) only support a single type. Therefore, it is your responsibility to convert operands to proper types. On the plus side, ReasonML will warn you at compile time if you forget to do so. That’s a benefit of static typing.

Comments  

Before we get into values and types, let’s learn comments.

ReasonML only has one way of writing comments:

/* This is a comment */

Conveniently, it is possible to nest this kind of comment (languages with C-style syntax are often not able to do that):

/* Outer /* inner comment */ comment */

Nesting is useful for commenting out pieces of code:

/*
foo(); /* foo */
bar(); /* bar */
*/

Booleans  

Let’s type in a few boolean expressions:

# true;
- : bool = true
# false;
- : bool = false
# !true;
- : bool = false
# true || false;
- : bool = true
# true && false;
- : bool = false

Numbers  

These are integer expressions:

# 2 + 1;
- : int = 3
# 7 - 3;
- : int = 4
# 2 * 3;
- : int = 6
# 5 / 3;
- : int = 1

Floating-point expressions look as follows:

# 2.0 +. 1.0;
- : float = 3.
# 2. +. 1.;
- : float = 3.
# 2.25 +. 1.25;
- : float = 3.5

# 7. -. 3.;
- : float = 4.
# 2. *. 3.;
- : float = 6.
# 5. /. 3.;
- : float = 1.66666666666666674

Strings  

Normal string literals are delimited by double quotes:

# "abc";
- : string = "abc"
# String.length("ü");
- : int = 2

# "abc" ++ "def";
- : string = "abcdef"
# "There are " ++ string_of_int(11 + 5) ++ " books";
- : string = "There are 16 books"

# {| Multi-line
string literal
\ does not escape
|};
- : string = " Multi-line\nstring literal\n\\ does not escape\n"

ReasonML strings are encoded as UTF-8 and not compatible with JavaScript’s UTF-16 strings. ReasonML’s support for Unicode is also worse than JavaScript’s – already limited – one. As a short-term workaround, you can use BuckleScript’s JavaScript strings in ReasonML:

Js.log("äöü"); /* garbage */
Js.log({js|äöü|js}); /* äöü */

These strings are produced via multi-line string literals annotated with js, which are only treated specially by BuckleScript. In native ReasonML, you get normal strings.

Characters  

Characters are delimited by single quotes. Only the first 7 bits of Unicode are supported (no umlauts etc.):

# 'x';
- : char = 'x'
# String.get("abc", 1);
- : char = 'b'
# "abc".[1];
- : char = 'b'

"x".[0] is syntactic sugar for String.get("x", 0).

The unit type  

Sometimes, you need a value denoting “nothing”. ReasonML has the special value () for this purpose. () has its own type, unit and is the only element of that type:

# ();
- : unit = ()

In contrast to null in C-style languages, () is not an element of any other type.

Among other things, the type unit is used for functions with side effects that don’t return anything. For example:

# print_string;
- : (string) => unit = <fun>

The function print_string takes a string as an argument and prints that string. It has no real result.

Converting between basic types  

ReasonML’s standard library has functions for converting between the basic types:

# string_of_int(123);
- : string = "123"
# string_of_bool(true);
- : string = "true"

All of the conversion functions are named as follows.

«outputType»_of_«inputType»

More operators  

Comparison operators  

The following are comparison operators. They are part of the few operators that work with several types (they are polymorphic).

# 3.0 < 4.0;
- : bool = true
# 3 < 4;
- : bool = true
# 3 <= 4;
- : bool = true

You cannot, however, mix operand types:

# 3.0 < 4;
Error: Expression has type int but expected type float

Equality operators  

ReasonML has two equality operators.

Double equals (equality by value) compares values and does so even for reference types such as lists.

# [1,2,3] == [1,2,3];
- : bool = true

In contrast, triple equals (equality by reference) compares references:

# [1,2,3] === [1,2,3];
- : bool = false

== is the preferred equality operator (unless you really want to compare references).