Today I Learned

A Hashrocket project

18 posts about #reasonml

Single arg pattern matching with the fun operator

Reasonml has pattern matching in specific syntaxes and one of those syntaxes is the fun operator which helps you define multiple patterns for single argument functions.

let something = 
  fun
  | "hello" => "world"
  | "busta" => "rhymes"
  | x => "something else"

when called:

something("hello")
/* results in "world" */

This doesn’t work for multiple arguments however, so when you see something like this:

let add =
  fun
  | (1, 1) => 3
  | (x, y) => x + y;

just remember that the single argument in this case is a tuple, called like this add((1, 3)).

Generate Starter ReasonML Projects With bsb

With the latest bs-platform installed, you should have access to the bsb command which contains a number of options — including -themes.

$ bsb -themes
Available themes:
basic
basic-reason
generator
minimal
node
react

This is a list of themes (read: templates) that can be used to generate a starter project.

For example, if you’d like a basic project structure geared toward writing Reason, run the following:

$ bsb -init my-project -theme basic-reason

Or if you’d like to get started with reason-react, give this a try:

$ bsb -init my-reason-react-project -theme react

source

Render a list of jsx elements with ReasonReact

In javascript/jsx I can just create a list of jsx elements and place that list into the jsx.

const thingDivs = things.map((thing) => (
    <div>{thing.name}</div>
)

return (<div>
  {thingDivs}
</div>)

But ReasonReact expects the variable to be of type ReasonReact.reactElement, and so we have to convert the list of jsx nodes we create to an element with the function ReasonReact.array.

let thingDivs = List.map((thing) => 
  <div>ReasonReact.string(thing.name)</div>
, things)

<div className="parentContainer">
  (ReasonReact.array(Array.of_list(thingDivs))
</div>

ReasonReact.array returns a value of type ReasonReact.reactElement.

Data Structures With Self-Referential Types

ReasonML has a powerful type system that allows us to create types that represent all sorts of things. For data structures like linked lists, we need a sort of recursive type, a type that can reference itself — a self-referential type.

Reason’s type system allows us to define types that reference themselves. Combine that with type arguments and variants — we can create a type definition to represents something like a linked list.

type linked_list('a) =
  | Empty
  | Node('a, linked_list('a));

A linked list is a chain of nodes. It can be an empty list, hence the first part of the variant. Otherwise, it is a node that has some data and then points to another linked list (chain of nodes).

The 'a part is a type argument. When creating a linked list, we can decide what type the 'a will be. Here is an int-based linked list:

let my_list: linked_list(int) = Node(25, Node(27, Empty));
/* my_list = [25] => [27] => [] */

Output Emojis 🔥 with ReasonReact

I’m using ReasonReact on top of ReasonML and I’m trying to output some emojis like this:

      <div>(ReasonReact.string("Fire 🔥"))</div>

but the output is:

Fire 🔥

The solution I found is to use the funky looking “Js string” instead of double quotes. The Js string in ReasonML has curlies and pipes with a js in the middle {js| Fire 🔥 |js}

In context it looks like this:

<div>(ReasonReact.string({js| Fire 🔥 |js}))</div>

And now I can see some fire:

Fire 🔥 

Create A Stream From An Array In ReasonML

There are functions in the Stream module for turning a variety of data structures into streams — lists, input channels, etc.

What if you have an array?

The Stream.from function lets you define a function for custom fitting data structures into streams. Let’s take a look:

let pokemons = [| "bulbasaur", "charmander", "squirtle" |];

let poke_stream: Stream.t(string) =
  Stream.from(i =>
    switch (pokemons[i]) {
    | pokemon => Some(pokemon)
    | exception (Invalid_argument("index out of bounds")) => None
    }
  );

The function takes the current index and needs to either return Some('a) with the corresponding value or None if the stream is empty.

With that, we now have a stream on which we can invoke any of the stream functions.

switch (Stream.next(poke_stream)) {
| pokemon => print_endline(Printf.sprintf("Next Pokemon: %s", pokemon))
| exception Stream.Failure => print_endline("No pokemon left")
};

Dynamically Create A Printf String Format

Formatting a string with Printf requires defining a format for that string.

let str = Printf.sprintf("%6s", "dope");
/* str => "  dope" */

The format is the first argument. At compile-time it is interpreted as a format6 type value.

So, what if you want a dynamically created format value? Simply concatenating some strings together won’t do it because then the type will be string and that’s not going to compile.

The Scanf.format_from_string function can help.

let some_num = 6;
let format_str = "%" ++ string_of_int(some_num) ++ "s";
let format = Scanf.format_from_string(format_str, "%s");

let str = Printf.sprintf(format, "dope");
/* str => "  dope" */

We can convert our string that has the appearance of a format into an actual format6 type. To do this, we have to tell format_from_string what types each of the formats is going to have — hence the second argument %s.

source

Break Out Of A While Loop In ReasonML

The while construct is a great way to loop indefinitely. You may eventually want to break out of the loop. For that, you are going to need to invalidate the while condition. One way of going about this is creating a mutable ref, changing it from true to false when a certain condition is met.

let break = ref(true);

while (break^) {
    switch (Unix.readdir(currentDir)) {
    | exception End_of_file => break := false
    | item => print_endline(item);
    }
};

Here we have a mutable ref called break which starts as true. This is our while condition. Its actual value can be referenced by appending the ^ character — hence break^. Once a certain condition is met inside our while block, we can set break to false using the := operator.

The above code snippet can be seen in full details here.

source

Seeding And Generating Random Integers In ReasonML

It is easy enough to generate a series of random numbers using the Random module’s int function.

Random.int(10);

This will generate a random integer between 0 and 9.

You may notice that the randomness is the same each time you run your program. That is because you have fixed seed. To make sure you have a different seed each time your program runs, you can initialize the random number generator with something different at each run, such as the current time.

Random.init(int_of_float(Js.Date.now()));

See a live example here.

String Interpolation With Quoted Strings

Stapling strings together with the ++ operator can be tedious and clunky. If you have string variables that you’d like to interpolate, you can piece them together much more easily using quoted strings.

We can get close to a solution with the standard quoted string syntax.

let greeting = (greetee) => {
  {|Hello, $(greetee)!|}
};

Js.log(greeting("World")); // => "Hello, $(greetee)!"

This isn’t quite right though. We have to take advantage of a preprocessing hook provided by Bucklescript. The j hook supports unicode and allows variable interpolation.

let greeting = (greetee) => {
  {j|Hello, $(greetee)!|j}
};

Js.log(greeting("World")); // => "Hello, World!"

To use this pre-processor we have to include j in the quoted string like so {j|...|j}.

See a live example here.

Defining Variants With Constructor Arguments

In Helping The Compiler Help Us With Variants, I introduced the concept of variants with a basic example of how to define and use one. The fun doesn’t stop there.

We can take variants a step further by defining them with constructor arguments.

type listActions =
  | Length
  | Nth(int);

The second variant is defined such that it is paired with some extra data — a single int argument.

Here is how we use that variant in our code:

let performListAction = (l: list(int), action: listActions) => {
  switch(action) {
  | Length => List.length(l)
  | Nth(n) => List.nth(l, n)
  }
};

performListAction([7,8,9], Nth(1)); /* 8 */
performListAction([1,2,3], Length); /* 3 */

Our switch statement not only matches on that variant, but it makes the int argument available as a value we can consume in that step of the switch.

source code

Helping The Compiler Help Us With Variants

ReasonML gives us something called a variant which is similar to what other language call enums and union types. By defining a variant, we give the compiler some information about exactly what values a variable can take on — its allowed variants.

Here we define the kinds of roles that users in our system can have as well as a user type for representing individual users:

type userType =
  | Student
  | Teacher
  | Admin;

type user = { role: userType, id: int };

And here is how we might use it in some authorization code:

let canCreateClasses(u: user) {
  switch(u.role) {
  | Student => false
  | Teacher => true
  };
};

We’ve neglected to include Admin in our switch statement. The compiler will inform us of this with a warning.

this pattern-matching is not exhaustive. Here is an example of a value that is not matched: Admin

source code

Inline Component Styles With Reason React

If you’ve written much React.js, the following may look familiar:

<span style={{
  width: "10px",
  height: "10px",
  backgroundColor: "rgb(200, 64, 128)"
}} />

This is how we do inline styles with JSX in JavaScript.

When it comes to doing inline styles with JSX in our ReasonML code, the best approach for now is to use a make function for styles provided by the React DOM bindings.

<span style=(
  ReactDOMRe.Style.make(
    ~width="10px",
    ~height="10px",
    ~backgroundColor="rgb(200, 64, 128)",
    ())
)/>

source

Quickly Bootstrap A React App Using Reason

The ReasonML language and ecosystem is great for developing React apps. As you might expect from the React community, there is a set of reason-scripts for a ReasonML/React project which works similarly to the standard create-react-app scripts.

First, you need to install the BuckleScript platform and this must be done using npm.

$ npm install -g bs-platform

From there, it is a matter of using the yarn create command to generate a React app that uses the aforementioned reason-scripts.

$ yarn create react-app my-reason-react-app --scripts-version reason-scripts

Next steps from here are documented in the README.md and should be familiar to anyone who has used create-react-app in the past.

Multi-Argument Functions As Syntactic Sugar

When writing a multi-argument function, like the following adder function:

let adder = (x, y) => x + y;

adder(2, 3); /* => 5 */

We are utilizing a syntactic sugar of the function syntax. The same function can be written as such:

let adder = (x) => (y) => x + y;

adder(2, 3); /* => 5 */

As you can see, we can apply the function in the same way.

This is useful because it means we can partially apply (or curry) our functions to create other functions.

let adder = (x, y) => x + y;
let twoAdder = adder(2);

twoAdder(5); /* => 7 */

source code

Exhaustive Pattern Matching Of List Variants

ReasonML’s switch expression allows for powerful pattern matching. When using switch to pattern match against a list, the compiler will be sure to warn you if you haven’t accounted for all variants of a list.

let getFirst = (numList: list(int)): int => {
  switch(numList) {
  | [first, ...rest] => first
  };
};

this pattern-matching is not exhaustive. Here is an example of a value that is not matched: []

The compiler knows that a list can either be 1) empty ([]) or 2) contain at least one value and another list ([a, ...rest]). To ensure all variants are accounted for, we can include the [] case in our switch.

let getFirst = (numList: list(int)): int => {
  switch(numList) {
  | [] => -1
  | [first, ...rest] => first
  };
};

source code

String Interpolation With Integers And Sprintf

ReasonML’s Printf module comes with a number of functions for formatting values of various types. The sprintf function allows for string interpolation.

let red = 64;
let green = 256;
let blue = 128;
let alpha = 1;

let color =
  Printf.sprintf("rbga(%i, %i, %i, %i)", red, green, blue, alpha);

Js.log(color);

It functions the same as fprintf but instead of outputting the result to some channel, it returns a string. It enforces type checking as well — the %i is specifically for integers, so using that with a type other than an integer will result in a compilation error.

See the Printf docs for more details.

source code

Pattern Matching On Exceptions

ReasonML supports powerful pattern matching through the switch statement. This even includes pattern matching against exceptions that may arise as a way of catching and handling those exceptions.

let values: list(int) = [1,3,5,7,9];

let getValue = (list, index) => {
  switch (List.nth(values, index)) {
  | value => value
  | exception Failure("nth") => 0
  | exception Invalid_argument("List.nth") => 0
  };
};

getValue(values, 0); /* 1 */
getValue(values, 1); /* 3 */
getValue(values, 5); /* 0 */
getValue(values, -1); /* 0 */

The List.nth docs detail what happens in the two kinds of out of bounds scenarios that would raise an error — Failure and Invalid_argument. You can pattern match against those by declaring the respective cases as exception instances and then returning the desired values in response.

source code