First ReasonML production app - lessons learned
Tags: reason, reactnative, react
Published: 2019-03-06 00:00:00

---
title: First ReasonML production app - lessons learned
published: true
date: "2019-03-06"
tags: reason, reactnative, react
description: What we learned creating our first ReasonML app
---

Over the last 5 months, we at [Codeheroes](https://codeheroes.io/) had a chance to work on a mobile application for both iOS and Android as well as web-based panel providing "admin" functionality for a mobile app. We always want to push our skills further and create better and more reliable software with every next project so we went with ReasonML for both applications.

> If you want to learn more about Reason I highly recommend official documentation and a great book by Dr. Axel Rauschmayer - [Exploring ReasonML and functional programming](http://reasonmlhub.com/exploring-reasonml/)

I had prior experience with Reason but the rest of the team (one junior and two developers with about two years experience each) had only worked with typed JavaScript using Flow, React and React Native. This project was not only our first Reason project as a team (previously I was writing parts of another app in Reason to test if it suits our needs) but also I had to learn and support other developers on their Reason path.

I want to share our experience and lessons learned from creating mobile and web apps in ReasonML!

## Why Reason?

We believe in a statically typed and functional approach as we worked with Flow previously. We also try to write software in an as much functional way as it makes sense in JavaScript. Additionally, our backend is written in Scala so our Backend Developers influence our way of thinking a lot.

I was thinking a lot about introducing Elm to our projects. The language is very hermetic and in my opinion, it would be impossible to create a project using all our previous React experience and technology we developed over the years.

When I learned about Reason somewhere in 2017 it was not mature enough to use it in production then. But in 2018 a lot of Reason community effort with tooling, bindings, talks, blog posts made the language mature enough to test it as a part of our existing JavaScript application. After that, as it went well it convinced us it's reasonable to go with Reason for the whole project.

In case of some real problems, we knew we can go back to JavaScript to finish the project on time. Fortunately, we hadn't had to do so. The last 5 months only convinced us it was a really good decision.

One of the things we were afraid of was interoperability with JavaScript...

## 1. Don't be afraid of bindings

The core functionality of the application is a medical Bluetooth device for women. They can connect the device to application and "play" simple games using their pelvic muscles.

We knew we had to use a few libraries that don't have existing bindings. The two most significant were: [react-native-ble-plx](https://github.com/Polidea/react-native-ble-plx) which we used for Bluetooth connection and [react-native-game-engine](https://github.com/bberak/react-native-game-engine) which provides nice API we used for creating games controlled by Bluetooth device.

Bindings forced us to define types for every function we used. It's the same story as Flow or Typescript type definitions but it gives us much more!

In Flow you can omit types, you can do whatever you want and if you decide to write types (or use already existing types from [flow-typed](https://github.com/flow-typed/flow-typed)) they can lie to you not reflecting the real API. They are not implementation they just type annotations. In Reason, you have to create bindings which are both type annotations and actual implementation of how we want to connect to existing JavaScript code. Of course, you can create bindings that lie about the API but it comes a lot quicker during development in Reason than in Flow.

You can disagree with me 😉.

Another cool Reason feature are abstract types. Those are types that don't have an internal structure. You define them as follows:

```reasonml
/* abstract type */
type someComplexJavaScriptType;

/* normal record type */
type person = {name: string, age: int};
```

You can create a type that is only passed from function to function. You don't have to care if it's a string, int or some complex object, and what field it has. It exists only in bindings. Here is an example of [react-native-ble-plx](https://github.com/Polidea/react-native-ble-plx) bindings using abstract types:

```reasonml
type bleManager;
[@bs.module "react-native-ble-plx"] [@bs.new]
external createBleManager: unit => bleManager = "BleManager";

type subscription;
[@bs.send] external removeSubscription: subscription => unit = "remove";

[@bs.send]
external onStateChange:
  (bleManager, string => unit, bool) => subscription = "onStateChange";
```

First, we define `bleManager` type and don't care about its internals. Then we have a function to create it from thin air. Great. The same goes with `subscription` type. We know `onStateChange` function returns subscription but we don't have to care what it is. We need only to know there is `removeSubscription` function to remove a subscription.

Once we're forced to create those bindings (and the process of writing them is not hard nor time-consuming) we have a nice place to slightly adjust the library to our needs. We can create an intermediate layer in which we can, for example, rearrange function arguments order for fast-piping or make them labeled, we can compose one or more functions together, we can model the API for our application use case. Without bindings, API we created around those libraries would be much worse.

Of course, this makes it harder to share bindings as an open-source project but I think it should never be the primary goal if you're forced to create bindings in your application code.
It's great to do that so others can benefit but I believe it's the second step. First, create bindings for any functionality you need, then write your application using them so you can validate if they are any good, then publish it as open-source and share with the community to gather feedback from other developers.

## 2. Render props, using let-anything and bs-epitath, are awesome

Render props is popular [React pattern](https://reactjs.org/docs/render-props.html). It allows you to reuse component logic in multiple places. A popular use case is `react-apollo` query component allowing you to create GraphQL queries. The problem is it makes your component bigger and harder to read. If you have one such component it's ok but if you have two or more you are creating that nested callback monster.

PPX to the rescue! PPX is something called a syntax rewriter. It's OCaml/ReasonML compiler extension which allows you to create compile-time macros.
One of such is [let-anything](https://github.com/jaredly/let-anything) - created by Jared Forsyth. Together with [bs-epitath](https://github.com/Astrocoders/bs-epitath/) from Astrocoders, it gives us real superpower. Let's look at React Context render props example:

```reasonml
/* without let-anything and bs-epitath */
let component = ReasonReact.statelessComponent(__MODULE__);
let make = _ => {
  ...component,
  render: _ => {
    <Context.Consumer>
      ...{context =>
        <BsReactNative.Text>
          {ReasonReact.string("Logged as: " ++ context.user)}
        </BsReactNative.Text>
      }
    </Contect.Consumer>
  },
};

/* with let-anything and bs-epitath */
let component = ReasonReact.statelessComponent(__MODULE__);
let make = _ => {
  ...component,
  render: _ => {
    let%Epitath context = children =>
      <Context.Consumer>
        ...children
      </Contect.Consumer>;

    <BsReactNative.Text>
      {ReasonReact.string("Logged as: " ++ context.user)}
    </BsReactNative.Text>;
  },
};
```

Sweet, right?

But be aware! We fall in the trap of overusing it in large components with not only data render props components but combined with view components.

> [Latest changes](https://github.com/Astrocoders/bs-epitath/pull/2) to bs-epitath will make it almost impossible to write such code as presented below. But since they want to support old and new syntax please be cautious!

```reasonml
/* don't do this, please */
let component = ReasonReact.statelessComponent(__MODULE__);
let make = _ => {
  ...component,
  render: _ => {
    let%Epitath context = children =>
      <Screen>
        <Section>
          <Card>
            <Context.Consumer>
              ...children
            </Contect.Consumer>
          </Card>
        </Section>
      </Screen>;

    /* real part of the component */
  },
};
```

While it's valid to do so, I think it introduces a lot of indirection and makes things harder to read. If you want to learn more, Gabriel Rubens Abreu wrote a great post - [Render Props composition for ReasonML is here](https://medium.com/astrocoders/render-props-composition-for-reasonml-is-here-b9c004ca9fcb) that describes the concept in details.

When starting with a new language, it's often difficult to learn best practices and deeply understand how to model your application. It was true for us, and we learned about it when working on the core functionality of the app...

## 3. Create more modules hiding your implementation details.

When we created functionality around Bluetooth connection we had to gather samples send by the device. We used them for controlling games and for sending them to our backend for further analysis. While sending samples to the backend is rather easy and requires little or no interaction with samples, the game part is rather complex as we want to process samples in various ways.
Iterate over samples applying some transformation to part of the samples, get samples in some timeframe, find spikes in a list of samples and much, much more.

We failed but not creating a dedicated `Sample` module. It should have `sample` type and all functions we would like to use across the whole application. It was a really bad decision that impacted the development of every part relying on that data. We had functions in many modules, many implementations of the same or similar function made by different developers. In general, it was a mess.

Lesson learned here - create modules for your data types to hide the implementation. Let's assume you have a simplified sample that consists of a timestamp and some value gathered in that time. Example module would look something like this:

```reasonml
/* Sample.re */

type t = (float, float);
type samples = list(t);

let make = (time, value) => (time, value);

let getTime = sample => sample->fst;
let getValue = sample => sample->snd;

let mapTime = (sample, fn) => {
  let (time, value) = sample;
  (fn(time), value);
};

let mapValue = (sample, fn) => {
  let (time, value) = sample;
  (time, fn(value));
};

/* more complex functions... */
```

Later, you decide that tuple is not a suitable data structure for your use case and you want to change it. Let's assume record because you have more than two elements tuple. You change only lower-level functions interacting with the `type t` and everything works as expected. No need for going through every module using `Sample`. One file, one commit. Done.

```reasonml
/* Sample.re */

type t = {time: float, value: float};
type samples = list(t);

let make = (time, value) => {time, value};

let getTime = sample => sample.time;
let getValue = sample => sample.value;

let mapTime = (sample, fn) => {
  {...sample, time: fn(sample.time)};
};

let mapValue = (sample, fn) => {
  {...sample, value: fn(sample.value)};
};

/* other functions... */
```

This is a simple example that was most significant to us and was probably the worst decision in the whole development but we learned a lot from that and wouldn't make the same mistake twice.

## 4. react-navigation is hard, but with Reason, it's not that hard

In our previous React Native application, we had a hard time around [react-navigation](https://reactnavigation.org/). It's really hard to make it statically typed and sound in Flow. Making refactor to screen names and props passed between screens caused us a lot of headaches during the development of a previous application.

When we started work on this application I was a little worried about how it would go. There were no good bindings to react-navigation at a time. But thanks to initial work on [bs-react-navigation](https://github.com/callstackincubator/bs-react-navigation) by guys at Callstack we had something we could work on.

Here are three parts making whole navigation a lot easier.

> 1. Explicitly passed navigation to every screen which wants to change currently active screen
> 2. Variant describing all available screens and their params
> 3. `getScreen` function rendering currently active screen based on screen variant

A simplified version looks something like this:

```reasonml
/* our screen type */
type screen =
  | Products
  | Product(string);

/* get screen function rendering currently active screen */
let getScreen = (route, navigation) => {
  switch(route) {
    | Products =>
      (
        <ProductsScreen navigation />,
        screenOptions(~title="Products", ())
      )
    | Product(id) =>
      (
        <ProductScreen id navigation />,
        screenOptions(~title="Product", ())
      )
  };
};

/* example screens creating our application */
module ProductsScreen = {
  let component = ReasonReact.statelessComponent(__MODULE__);
  let make = (~navigation, _) => {
    ...component,
    render: _ =>
      <BsReactNative.ScrollView>
        <Product onPress={id => navigation.push(Product(id))} />
        /* other products... */
      </BsReactNative.ScrollView>
   };
};

module ProductScreen = {
  let component = ReasonReact.statelessComponent(__MODULE__);
  let make = (~id, ~navigation, _) => {
    ...component,
    render: _ =>
      /* product query */
      <BsReactNative.View>
        /* product details... */
      </BsReactNative.View>
   };
};
```

It makes it almost impossible to pass wrong params, forgot about something and in the process of adding more params or changing existing routes
you know every place you have to adjust. And if you make them, the compiler tells you what's wrong and as soon as it compiles - in 99% of cases it works as expected.

## 5. Don't be afraid of polymorphic variants.

We used [graphql_ppx](https://github.com/mhallin/graphql_ppx) and [reason-apollo](https://github.com/apollographql/reason-apollo) for GraphQL client-server communication. In our schema, we have a lot of GraphQL Enum types. From things like application locale to things like available games and their configurations. In graphql_ppx generated code enums are polymorphic variants.

When we started writing application we were "scared" of polymorphic variants and decides to create normal variant types for each enum we use in our GraphQL schema. We created a module for each one with a bunch of functionality for converting them back and forth. We had to convert them to every place of the application. From polymorphic variant to variant, from variant to string, from variant to i18n message. It was a lot of duplicated logic only because we were afraid to use polymorphic variants. And what about adding or removing something in API? We had to change our types twice, both polymorphic variants and normal variants.

We learned polymorphic variants are just as fine as normal variants. They give you fewer guarantees and code is harder to debug since compilation errors can pop up in strange places not directly related to please when you use them. Despite all that you shouldn't be afraid. But remember, they are more expensive than normal variants, so use them with caution after all 😉.

My rule of thumb is - if you only pass some variant from the API to the view, it's totally fine to use a polymorphic variant. If they live only in your application (like in navigation) it's better to use normal variants.

## Community is great, open-source is great!

Without great projects like [bs-react-native](https://github.com/reasonml-community/bs-react-native), [re-formality](https://github.com/alexfedoseev/re-formality), [graphql_ppx](https://github.com/mhallin/graphql_ppx) and [reason-apollo](https://github.com/apollographql/reason-apollo), [reason-apollo](https://github.com/apollographql/reason-apollo), [bs-react-navigation](https://github.com/callstackincubator/bs-react-navigation) and [bs-react-intl](https://github.com/alexfedoseev/bs-react-intl) our work would be much harder or maybe even not possible. We wouldn't choose to go with Reason without those libraries. I want to thank everyone involved in making those libraries and other open-source libraries we used.

If you want to talk more about our work or Reason, DM me on <a href="https://twitter.com/_cichocinski">Twitter</a>!