Rajat S

@geeky_writer_

Get Reason-able with ReasonML — Part 3

Pattern Matching, Type Parameters, Exceptions, Loops, Modules, and BuckleScript

This is Part 2 of a 3 part series where I will help you make sense of ReasonML and all of its syntax and semantics. I will go through everything from basic datatypes to declaring functions in ReasonML.

Make sure that you read Part 1 and Part 2 of this series before going further. They are all connected.

Pattern Matching using Switch

Pattern matching is one of the best features of the language. It mainly does two things:

  • Check for a certain structure of a value.
  • Extract parts of a value.

With pattern matching, we can do many great things like piping multiple patterns into a single case!

Thanks to structural equality, pattern matching works well with data structures like tuples, lists, variants, and records.

Pattern Matching Tuple

# switch (rajat) {
| ("Learn ReasonML", false) => "It's Awesome"
| ("Learn ReasonML", true) => "It really is awesome"
};

But this will make our app exhaustive. We need to use _ to match the unmatched values for this type. Using a name instead of an underscore allows us to extract parts of the tuple.

# switch (rajat) {
| (_, true) => "Its Awesome"
| (text, false) => "It really is awesome. " ++ text
};
- string = "It really is awesome. Learn ReasonML"

Pattern Matching Lists

In case of Lists, we can perform pattern matching on the exact list.

# switch (["a", "b", "c"]) {
| ["a", "b", "c"] => true
| _ => false
};

The best thing about pattern matching with Lists is that ... can be used to extract the first and last element of the List.

# switch (["x", "y", "z"]) {
| [head, ...tail] => print_endline(head)
| [] => print_endline("Empty list")
};

Pattern Matching Arrays

When pattern matching arrays, we can only do so for arrays of specific lengths. The values in the array are matched using structural equality. We can either use an underscore or we can extract the array elements using a name.

# switch ([|"a", "b", "c"|]) {
| [|"a", "b", _|] => print_endline("a, b and something")
| [|_, "x", "y"|] => print_endline("something, " ++ x ++ y)
| _ => print_endline("An Array")
};

Pattern Matching Records

First, I will create a record by declaring the type and then bind it to a name.

# type todo = { text: string, checked: bool};
# let myTodo = { text: "Learn ReasonML", checked: true};

Now, I could extract the text by matching the exact value. But instead, I will try something else and extract the text using the name description.

# switch (myTodo) {
| {text, checked: true} => "It is awesome: " ++ text
| {text, checked: false} => "You won't regret it!" ++ text
};
- : string = "It is awesome: Learn ReasonML"

Pattern Matching Variants

Lets create a variant called hero with tags DC and Marvel.

# type hero = DC(string) | Marvel(string);
# let dc = DC("Batman");
# let marvel = Marvel("Iron Man");

Using pattern matching, I can check the tags and extract any part of the variant.

# switch (dc) {
| DC(text) => "I am " ++ text
| Marvel(text) => "I am " ++ text
};
- : string = "I am Batman"

If you want to match more than one item and return them as the result, you can do something like this:

# switch ("Batman") {
| "Superman" | "Batman" | "The Flash" => "DC Comics"
| _ => "Marvel Comics"
};
- : string = "DC Comics"

Type Parameters

Types can also accept parameters. Parameters can be compared with generics in other languages. When you create a list, you first need to create a type list that receives a data type. Here, the data type is a type parameter.

# let rajat: list(string) = ["Batman", "Superman"];

The compiler can even inverts the type parameter of a list.

But why do we need this?

Using type parameters, we can create the a new type that can accept as many parameters as we want.

Basically, type can turn into a function that takes in parameters and return a new type.

# type hero('a) = ('a, 'a);
# let heroOne: hero(string) = ("Superman", "Clark Kent");

This way we can avoid repetition while creating more types.

Also note that the type parameters are only valid if they start with a “`“, followed by a character or word.

Mutability in let bindings

let bindings are immutable by default. Once a binding refers to a value, it cannot refer to anything else. But you can circumvent this issue by creating a new binding that has the same name and shadows the previous binding.

# let person = "Clark Kent";
# print_endline(person); /* Prints "Clark Kent" */
# let person = "Superman";
# print_endline(person); /* Print "Superman" */

There is also a way to turn let bindings mutable by using a reference to wrap the actual value.

# let foo = ref(5);
let foo: ref(int) = {contents: 5};

Now if I want to change the data inside of foo, this is how I will do it:

# foo := 6;
- : unit = ()
# foo;
- : ref(int) = {contents: 6}

The value of foo is changed from 5 to 6. To retrieve the value of reference, I need to use ^ character.

# foo^;
- : int = 6

Exceptions

In Reason, Exception is a kind of variant that you get when trying to find something inside an empty list.

# List.find(x => x == "Rajat", []);
Exception: Not_found.

That’s not all! You can create your own exceptions using the raise function. To catch exceptions in your app, use pattern matching.

# raise(Not_found);
Exception: Not_found.
# try (raise(Not_found)) {
| Not_found => "Oh Oh!"
};
= : string = "Oh Oh!"

We can directly match exceptions in a switch expression using exception.

# switch (List.find(x => x == "rajat", [])) {
| name => "Obtained"
| exception Not_Fount => "Not found"
};
- : string = "Not found"
# switch (List.find(x => x == "rajat", ["rajat"])) {
| item => "Obtained"
| exception Not_found => "Not found"
};
- : string = "Obtained"

There are not many uses for exception. In fact, you can instead use option.

Loops

For Loop

for loops are used to iterate from the first to the last value of a data structure.

# for (x in 1 to 5) {
print_int(x * 2);
print_string(" ");
};
2 4 6 8 10 - : unit = ()

The range needs to be valid and go from a lower value to a higher value. If you try to do it the other way, for will not do anything. To make for work in the reverse direction, replace to with downto.

While Loop

The while loops will keep operating as long as the condition given is true.

# let x = ref(0);
let x: ref(int) = { contents: 0};
# while (x^ < 5) {
print_int(x^);
x := x^ + 1;
};
01234- : unit = ()

Modules

Modules can be described as small blocks in your code. They allow us to encapsulate things like let bindings and types into logical entities.

Every file in Reason is a module. This is why name needs to be unique in each project.

Use the module keyword to create a modules in your Reason project. Make sure that the name of your module is capitalized.

# module Rajat = {};
`module Math` : { };
# module Math = {
let name = "rajat";
let age = 24;
};
`module Math`: { let name: string; let age: int };

To access anything that is inside a module, use the . notation.

# Rajat.name;
- : string = "rajat"
# Rajat.age;
- : int = 24;

. notation is really helpful here as we can access types stored inside the module as well. This way, the compiler will only look at the current module or its parent module.

Reason also allows us to open a module’s definition and refer to its contents without prepending the name.

To do so, we use the open keyword. This will open the module globally and import its definition in the current scope.

BuckleScript

Back in Part 1 of this series, I had said that Reason can be compiled into JavaScript using something called BuckleScript. Let’s see how this is actually done.

First we need to install BuckleScript into our system.

npm install -g bs-platform

With this package installed, I can not only compile my code into JavaScript, but also into native binary code.

Now I need to set up a new project using bs-platform

bsb -init reason-project -theme basic-reason

This command will create a new directory called reason-project in your system. If you take a look at the contents of this directory, you will notice that it shares a few similarities with a typical JavaScript project.

The only unique thing here, is the bsconfig.json file. This file is where we can configure the BuckleScript for this project.

If you look at the src directory of this project, you will see that it contains a Demo.re file. Let’s delete it and create a new file named Main.re. You can write any ReasonML code here and run the build script using NPM/Yarn. This will compile any file with .re extension into a matching JavaScript file. So our Main.re file will compile into Main.bs.js.

You can run this JavaScript file using node.

node src/Main.bs.js

You can also using start script to build it manually. This script will watch for file changes and compile accordingly.

What’s Next?

This series should be enough to get you started with ReasonML. But if you still want to know more, take a look at Reason’s official Docs here:

You can also take a look at Nik Graf’s Course on Egghead. It helped me a lot!

I am Rajat S, Technical Content Writer at GeekyAnts. Aspiring Coder who has a long way to go. A Die-Hard DC Comics Fan who loves Marvel Movies. Known for multi tasking.

Thanks for reading, and hopefully this was helpful! Please 👏 if you liked this post and follow me here and/or on Twitter to stay updated about new posts from me!

More by Rajat S

Topics of interest

More Related Stories