Matthias Le Brun
@bloodyowl
BlogTwitterGitHub

How ReasonReact.Router represents a URL

2019/02/09

ReasonReact embeds a small router out of the box.

Its API surface is smaller than most solutions you can find out there, but it leverage some languages data-structures in an interesting way, particularly in how it represents URL paths.

Here's how it looks:

type url = {
  path: list(string),
  search: string,
  hash: string,
};

The most interesting part here is the way path is represented: a list, with each item in it being a segment (separated by /).

Lists in OCaml/ReasonML are linked lists, which means they are composed roughly that way:

type list('a) =
  | Empty
  | One('a, list('a)); /* look! it's a recursive type */

The rest of the list (or tail) is another list! And you can loop through it using recursion and pattern matching:

let rec map = (list, f) =>
  switch (list) {
  | Empty => Empty /* we're at the end, return empty*/
  | One(x, rest) =>
    One(f(x), map(rest, f)) /* tranform x, and map the rest*/
  };

In ReasonML, there's syntactic sugar for that: the equivalent of Empty is [], and of One(x, rest) is [x, ...rest] (and x :: rest in OCaml).

So map would in reality look like:

let rec map = (list, f) =>
  switch (list) {
  | [] => []
  | [x, ...rest] => [f(x), ...map(rest, f)]
  };

In the context of a router, lists are pretty interesting too, as URL paths generally represent depth (e.g. /users/id is one level deeper than /users):

switch (url.path) {
| [] => <Home />
| ["me", ...subPath] => <Settings id=me.id subPath />
| ["users", id, "settings", ...subPath] => <Settings id subPath />
| _ => <NotFound />
};

You can handle subroutes in a descendant by passing down the tail of the path! And nice side effect: because you just take the list after an item, you don't allocate anything: you just pass tail the list itself.

Copyright 2019 - Matthias Le Brun