Today I Learned

hashrocket A Hashrocket project

44 posts about #reasonml surprise

Create A Map Of Strings In ReasonML

ReasonML has the Map.Make functor in its standard library which allows you to create a Map module with a specific key type. Here is how we can make a map module with string keys.

module StringMap = Map.Make(String);

We can then use that module to to create an empty map followed by adding key-value pairs to it.

StringMap.empty
|> StringMap.add("Morty", "Smith")
|> StringMap.add("Rick", "Sanchez")
|> StringMap.add("Scary", "Terry")
|> StringMap.iter((first, last) => {
  print_endline(Printf.sprintf("%s %s", first, last));
});
/*
Morty Smith
Rick Sanchez
Scary Terry
*/

See the live example.

Creating A 2D Array

In most languages if I wanted to create a two-dimensional array, I would utilize some nested looping construct to generate columns of rows. The ReasonML Array module abstracts this away.

let grid = Array.make_matrix(10, 10, 0);

grid
|> Array.iter(column => {
  column
  |> Array.iter(cell => {
    print_int(cell);
  });
  print_endline("");
});

/*
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
*/

The make_matrix function allows you to specify dimensions of a two-dimensional array with all positions initialized to the same value -- that third argument.

Modifying A String With blit_string

ReasonML's Bytes module has a function called blit_string. This function allows you to copy portions of a string into a destination byte sequence. It is a fairly low-level operation, so you have to provide a source string and provide an offset of that source string to start copying from. You then have to provide a properly sized byte sequence as well as the destination's starting offset and length of bytes to be copied.

Here is an example of how we can use blit_string to create a copy of the string with the first character removed.

let remove_first_char = (str: string): string => {
  let copy_len = String.length(str) - 1;
  let dst = Bytes.create(copy_len);
  Bytes.blit_string(str, 1, dst, 0, copy_len);
  Bytes.to_string(dst);
};

Notice that once the byte sequence has been copied over, we then need to convert it back into a string.

Generate A Native ReasonML Project With Pesy

Pesy is a CLI utility available from NPM that you can use to generate a ReasonML project that is ready for native compilation. It uses esy for the management of opam packages. It uses Dune for building your library code with the ReasonML and OCaml dependencies.

Assuming you already have pesy installed globally, create a directory for your project and then run:

$ pesy

A project will be generated that is out-of-the-box ready to compile native executables.

Using Optional Labeled Function Args In ReasonML

If you are constructing a function that takes some arguments, but one of those arguments has a reasonable default value, then you can use an optional labeled argument. Labeled arguments are those arguments prefixed with a ~. If you give the argument a default value, then it becomes optional.

let thing = (~a=1, b: int, c: int) => {
  a + b + c;
};

In this case ~a is a labeled argument. It is also optional and will default to 1 if not specified. The other two arguments, b and c, are positional arguments and thus required in order for the function to evaluate.

Here are two ways of using this function either by specifying ~a or excluding it so that it defaults to 1.

thing(~a=2, 1, 1)
|> string_of_int
|> print_endline /* 4 */

thing(1, 1)
|> string_of_int
|> print_endline /* 3 */

See more details here.

Two Ways To Find An Item In A List With ReasonML

The List module has the typical find function that you'd expect any enumerable type to include. It has a very similar find_opt function as well. The difference is in the return types.

When using List.find you'll have to deal with the possibility of a Not_found exception.

switch (List.find(item => item.id == id, my_list)) {
| exception Not_found => print_endline("Not found!")
| item => print_endline("Found it: " ++ item.name)
}

The List.find_opt function has a more familiar interface that doesn't require you to know the details of what exceptions could arise. All you want to know is if it was found or not. This is achieved by having an option('a) return type.

switch (List.find_opt(item => item.id == id, my_list)) {
| None => print_endline("Not found!")
| Some(item) => print_endline("Found it: " ++ item.name)
}

See the List module for more details.

Compile ReasonML With An OCaml Package Using Dune

In Compile Reason To Native With Dune, I showed how to compile a basic ReasonML file as a native executable using Dune.

Any non-trivial program will likely involve pulling in an OCaml dependency. For example, you may want to pull in Lwt. Assuming this package is available, whether you've manually downloaded it via opam or used something like esy, you'll want to let Dune know that Lwt is an available library.

;; dune
(executable
 (name hello_reason)
 (libraries lwt lwt.unix))

The modules in the Lwt package will now be globally available to your Reason code.

let () = {
  Lwt_main.run(
    Lwt_io.printf("Hello, Reason!\n")
  );
};

When Dune builds your code, it will include and compile Lwt.

See a full example here.

Compile ReasonML To Native With Dune

Dune is "a composable build system for OCaml" with out-of-the-box support for ReasonML. Dune can be used for a lot of things, but in simplest terms it can be used to compile ReasonML programs into native executables.

Considering the following ReasonML program.

/* hello_reason.re */
print_endline("Hello, Reason!")

We can then create the following Dune build file.

;; dune
(executable
 (name hello_reason))

If we then run dune build hello_reason.exe, then Dune will compile the hello_reason.re into a hello_reason.exe executable that can be found in build/default. Run it and see the output.

Read more in the Quickstart guide.

Wrap A ReasonReact Component For Use In JavaScript

Consider the following ReasonReact component for displaying a greeting.

let component = ReasonReact.statelessComponent("Hello");

let make = (~name, _children) => {
  ...component,
  render: _self =>
    <p> {ReasonReact.string("Hello, ")} {ReasonReact.string(name)} </p>,
};

If we will just be using this component in a ReasonML context, then that is all we need.

If we want this component available for use in a JavaScript file, we have a little more work to do. We need to define the shape of the component's props using a bucklescript directive and then wrap the component so that it gets appropriate exported for a JavaScript context.

Here is what that looks like.

[@bs.deriving abstract]
type jsProps = {name: string};

let default =
  ReasonReact.wrapReasonForJs(~component, jsProps =>
    make(~name=jsProps->nameGet, [||])
  );

Our only prop is name which is a string. The wrapReasonForJs function and accompanying binding to default mean that we can import the compiled JS-equivalent like so:

import Hello from 'src/components/Hello.bs.js';

See the docs for more details on Reason/JS interop.

Format The Current ReasonML File Within Vim

I'm editing a .re file within Vim. I haven't yet wired up refmt to something like ALE for automatic formatting on save. By the time I'm done with my changes, indentation is a mess.

I can still take advantage of refmt to clean up my file.

:!refmt --in-place %

Running that command in Vim will cause the current file to be formatted.

How does it work?

It shells-out to refmt which does all the heavy lifting. The --in-place flag means that the target file will be re-written by the formatted result. The % is a handy Vim shorthand for the path and name of the current file.

Two ways to write lists in OCaml

The :: operator in OCaml is used to create head tail lists.

[1; 2; 3;]

Can also be written as:

1 :: (2 :: (3 :: []))

And in fact, the AST of the first example is a nested data structure joined with the :: operator.

You can read more about lists here

The first way is convenient, but the second way is important because lists are pattern matched with the :: operator.

let pat_match_func numbers =
  match numbers with
  | first_number :: rest_of_numbers ->
    # do something with first number

Splitting a string with `Js.String.splitByRe`

In ReasonML you are not always in a JS context, but when you are, you can use Js.String.splitByRe for splitting.

let regex = [%re "/\|/"];

let results = Js.String.splitByRe(regex, "Apple|Bird|Turtle");

Js.log(results);

You can play around with it here.

As a sidenote, if you read the docs on the Str module it looks like there are Reason ways to do this, but I can't get anything in that module to work.

In a ReasonML "Try" window here I try calling:

Str.regexp("", "");

But just get the error

The module or file Str can't be found.
Are you trying to use the standard library's Str?
If you're compiling to JavaScript, use Js.Re instead.
Otherwise, add str.cma to your ocamlc/ocamlopt command.

Pretty confusing :/.

Variadic args in ReasonML with JS Interop

I have this Javascript function in my utils file.

const add = (...args) => { return args.reduce((x, a) => a + x, 0) }

export add;

I'd like to use it in my ReasonML program, but it has variadic arguments, and ReasonML is a typed language. As long as all the variadic arguments are of the same type, however, I can import it into ReasonML with the [@bs.splice] attribute.

[@bs.module "utils"] [@bs.splice] external add : (array(int)) => int = "add";

[@bs.splice] will transform the last argument from an array into a list of arguments.

In ReasonML

let a = add([|1, 2, 3|]);

In Javascript

var a = add(1, 2, 3);

Syntactically correct OCaml AST at the cmd line

I wrote a til about a week ago where I used refmt to convert reasonml to AST but the output is this:

[
  structure_item ([1,0+0]..[1,0+19])
    Pstr_module
    "Something" ([1,0+7]..[1,0+16])
      module_expr ([1,0+17]..[1,0+19])
        Pmod_structure
        []
]

While this is a good clue, it's not something I can copy and paste into my OCaml ppx file. It is not OCaml.

What's better is using the dumpast tool that comes with ppx_tools (installable with opam install ppx_tools).

Given a reason file:

module Something {}

When I run:

> cat target.re | refmt --parse re --print ml > target.ml 
> ocamlfind ppx_tools/dumpast ./target.ml

It returns:

[{pstr_desc =
   Pstr_module
    {pmb_name = {txt = "Something"};
     pmb_expr = {pmod_desc = Pmod_structure []}}}]

Each of the records it returns is missing a location field and an attributes field, but you should use Ast_helper to produce Parseetree records with the location and attributes fields defaulted for you.

Match ocaml versions with Reasonml with `opam`

This week I was trying to write a ppx in ocaml use it in my reasonml project but I was getting an error about mismatching Ocaml versions.

Ast_mapper: OCaml version mismatch or malformed input

When I checked my versions sure enough there was a mismatch:

> bsc
BuckleScript 4.0.0 (Using OCaml4.02.3+BS )
> ocaml -version
The OCaml toplevel, version 4.07

Reasonml and Bucklescript use an older version of ocaml, version 4.02.3. I had compiled my ppx binary however with 4.07.

Fortunately it is rather easy to switch ocaml versions using opam, the Ocaml package manager. If you have opam installed (on mac brew install opam) then you can use the switch command.

opam switch 4.02.3

Running opam switch without an argument will show you all the available versions.

Convert Reasonml to Ast at the command line

I'm curious what module Something {} looks like after being translated to the Ocaml AST.

I can use the Reasonml tool refmt to generate the ast for me at the command line with the --print ast command line flag.

echo 'module Something {}' | refmt --parse re --print ast

Which outputs:

[
  structure_item ([1,0+0]..[1,0+19])
    Pstr_module
    "Something" ([1,0+7]..[1,0+16])
      module_expr ([1,0+17]..[1,0+19])
        Pmod_structure
        []
]

Is This A Directory Or A File -- ReasonML

When compiling ReasonML natively, we have access to a variety of additional modules including the Unix module. We can interact with directories and files using functions on Unix.

let current_dir = Unix.opendir(Unix.getcwd());
let first_file = Unix.readdir(current_dir);
/* is first_file a directory or a file? */
Unix.closedir(current_dir);

Here we open the current working directory, grab the first thing out of that directory -- maybe it's a file, maybe it's a directory, maybe it is something else. Lastly, we close the directory.

let current_dir = Unix.opendir(Unix.getcwd());
let first_file = Unix.readdir(current_dir);

switch(Unix.stat(first_file)) {
| Unix.stats({ st_kind: Unix.S_REG }) => print_endline("Regular File")
| Unix.stats({ st_kind: Unix.S_DIR }) => print_endline("Directory")
| Unix.stats({ st_kind: Unix.S_LINK }) => print_endline("Link")
| Unix.stats({ st_kind: Unix.S_SOCK }) => print_endline("Socket")
| _ => print_endline("Something else")
};

Unix.closedir(current_dir);

There are a variety of kinds of files to switch on. Here, we are switching on Regular Files, Directories, Links, and Sockets. Everything else falls through.

See the Unix module docs for more details.

Suppress warnings from bucklescript compiler

The bsconfig.json file has a configuration called bsc-flags that are sent to the bucklescript compiler (bsc). That looks like this by default:

"bsc-flags": ["-bs-super-errors"],

This are a host of flags you could send to the bsc compiler, and you can see them all with:

> bsc -warn-help
...
  6 Label omitted in function application.
  7 Method overridden.
  8 Partial match: missing cases in pattern-matching.
  9 Missing fields in a record pattern.
 10 Expression on the left-hand side of a sequence that doesn't have type
    "unit" (and that is not a function, see warning number 5).
...

Note the numbers that correspond with each warning. The bsc command takes a -w flag and you can explicitly tell the compiler to ignore a warning by using a minus sign in front of the warning number, like this:

"bsc-flags": ["-w -8", "-bs-super-errors"],

To ignore all warnings, use -w -A.

To turn all warnings into errors use -w @A.

Per the bsc -help the default setting is:

+a-4-6-7-9-27-29-32..39-41..42-44-45-48-50-102

Use a Lodash function in ReasonML w/Interop

This is my attempt to get into javascript interop in ReasonML. Really, you probably have enough tools in ReasonML to avoid using any Lodash functions but I'm using it to learn.

[@bs.module "lodash"] external myMin : array('a) => 'a = "min";

let result = myMin([|1, 2, 3|]);
/* result is 1 */

The bs.module syntax is not very well documented and a little obtuse.

[@bs.module "lodash"] will turn into the require statement in JS.

external myMin is where you name the function you'll be using in Reason.

array('a) => 'a declares the type signature and return value.

= "min" is where we declare what the function is on the javascript side.

There are some parentheses missing that might help you read and parse this differently. Here is what the same statement looks like with added parentheses.

[@bs.module("lodash")] external myMin : (array('a) => 'a) = "min";

Reasonml Interpolation

Reasonml doesn't support string interpolation in it's primary string type, but for javascript interop they expose a

special tag quoted string interpolation

Using the {j| |j} operator and a $ for the variable.

It looks like this:

let name = "Chris";
let greeting = {j|Hi there $name|j};

Parens around the value also works:

let greeting = {j|Hi there $(name)|j};

Play with it more here

Another pipe operator, the fast pipe `|.`

Reasonml has a pipe operator |> that places a value into the first positional argument, like so:

> let result = 4 |> String.sub("abcdefg", 1)
"bcde"

This is different than the same operator in Elixir, but I get it. I just learned however that Reasonml has another pipe operator, the fast pipe |. that places the value into the first argument of the function, like this:

> let result = "abcdefg" |. String.sub(2, 4)
"cdef"

This seems to be evolving functionality with some special cases that you can learn more about in the bucklescript docs.

Stream A File Line By Line In ReasonML

We can use the Stream module in ReasonML to read a file getting each line on demand. Doing this requires two key insights. First, we can open a file as an input channel. Second, we can turn an input channel into a stream using Stream.from.

let file_in_channel = Pervasives.open_in('file.txt');

let file_stream =
  Stream.from(_i => {
    switch(Pervasives.input_line(file_in_channel)) {
    | line => Some(line)
    | exception(End_of_file) => None
    };
  });

file_stream |> Stream.iter(line => do_something(line));

The Pervasives module (which is open by default and is only prefixed above so as to be explicit) allows us to open the named file as an input channel with open_in. It also allows us to read lines off that file with input_line. We use Stream.from to create a custom stream that consumes the input channel line by line using input_line. We either get some line or we hit the end of the file. Lastly, we can do whatever we want with the stream, such as iterate over it.

See the docs for Pervasives and Stream for more details.

No-op reducer in Reason React

Generally in ReasonReact if you have a reducer component that component will have a reducer function that looks like this:

reducer: (action, state) => {
    switch(action) {
    | Change(newValue) =>
        ReasonReact.Update({value: newValue})
  }
}

ReasonReact.Update is a variant that constructs with a state value.

Another variant you can return from the reducer function is ReasonReact.NoUpdate, allowing you to not update the state if circumstances do not warrant it.

reducer: (action, state) => {
    switch(action) {
    | Change("UghWat") =>
        ReasonReact.NoUpdate
    | Change(newValue) =>
        ReasonReact.Update({value: newValue})
  }
}

Not if the user enters "UghWat" we won't update the state with that useless value.

Making Things Mutable In ReasonML

In ReasonML, things that we create with let are immutable -- which means that we can't change them.

let num = 5;

Once num is bound to 5 it is stuck with that value for the duration of it's scope.

ReasonML doesn't completely restrict us to immutability though. The ref construct allows us to bind a variable to a sort of box that holds a value. We can then look in the box and change what is in the box.

let num = ref(5); /* put 5 in the box */

Js.log(num^); /* use `^` to look in the box */

num := 3; /* remove 5, put 3 in the box */

We use ref to bind our variable to a box with some initial value. The := assignment operator allows us to change what's in the box. Anytime we want to refer to what's in the box, we postfix our variable with ^.

Also of note: while list instances are not mutable, array instances are.

source

Reformat Reason code with the `--in-place` option

The ability to format code automatically and in a standardized way is an accepted part of writing code today. I use a format tool in javascript (w/prettier), in golang, and in Elixir.

Reasonml has it's own format tool, refmt which is installable via npm:

npm install -g reason-cli@3.2.0-darwin

At the command line you should use the --in-place option to overwrite the specified file with the formatted code output by the tool.

> refmt --in-place src/App.re

Otherwise refmt writes to stdout, but rerouting that stdout to the same file will just truncate the file.

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