Today I Learned

A Hashrocket project

8 posts about #reasonml

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