Reselect, but in OCaml
Tags: ocaml, redux, monad, memoization
Published: 2021-10-19 18:42:44

*The post was originally written [here](https://sketch.sh/s/uOxem5pPQvTqYriqmKqUtp/), so you can go there to play with the code*

In this post, I'd like to share with you my implementation of [reselect](https://github.com/reduxjs/reselect) library and few related reflections. 

**Reselect is** a library to make reusable and memoized selectors. Which allows you to put minimal data inside your state. I like the simplicity and the power of that concept, so I decided to build something similar myself.

Let me first put the working implementation. Then we will use it.

```ocaml
let memo ?eq:(eq=(=)) fn =
    let last_arg_res = ref None in
    (fun arg ->
        match !last_arg_res with
        | Some (last_arg, last_res) when eq last_arg arg -> last_res
        | _ ->
            let new_res = fn arg in
            last_arg_res := Some (arg, new_res);
            new_res
    )

type ('a, 'b) t = ('a -> 'b)

let return res = (fun _ -> res)

let id a = a
 
let (>>=) fn bind_fn =
    fun arg -> arg |> bind_fn @@ fn arg
```

To make our code a little more elegant, we will prepare some utils.

```ocaml
let (>>) f1 f2 arg = f2 (f1 arg)
let percent all part =
  let part = float_of_int part in
  let all = float_of_int all in
  part /. all *. 100.
  |> int_of_float
```

Now we are ready to play. Imagine an app to learn words.

```ocaml
module Word = struct
  type t = {
    language: string;
    spelling: string;
    translation: string;
    learned: bool;
  }
  
  module Get = struct
    let lang {language; _} = language
    let spell {spelling; _} = spelling
    let transl {translation; _} = translation
    let learned {learned; _} = learned
  end
end

module State = struct
  type t = {
    current_language: string;
    words: Word.t list;
  }
  
  module Get = struct
    let cur_lang {current_language; _} = current_language
    let words {words; _} = words
    
    let cur_words t = 
      let lang = cur_lang t in
      let words = words t in
      words
      |> List.filter (Word.Get.lang >> (=) lang)
    
    let learned_cur_words t =
      let words = cur_words t in
      words
      |> List.filter (Word.Get.learned)
    
    let progress t =
      let words_len = cur_words t |> List.length in
      let learned_words_len = learned_cur_words t |> List.length in
      percent words_len learned_words_len
  end
end

let sample: State.t = {
  current_language="nl";
  words=[
    { language="nl"; spelling="banana"; translation="banaan"; learned=true; };
    { language="eo"; spelling="winter"; translation="vintro"; learned=true; };
    { language="nl"; spelling="kindness"; translation="vriendelijkheid"; learned=false; };
    { language="nl"; spelling="rainbow"; translation="regenboog"; learned=false; };
  ]
}

let progress = State.Get.progress sample
```

The state data is exposed through getters, which help us to easily get/calculate required data. It is extremely useful. But what if our *progress* getter could perform some heavy calculations? Performing the calculations every time is not the most efficient decision. Especially if state changes do not relate to data used inside *progress* getter. But we can rewrite the getter.

```ocaml
let (>>=) fn bind_fn = fn >>= (memo bind_fn) (* Without this line the ">>=" performs only a function composition, which is also nice *)

let progress =
  State.Get.cur_words >> List.length >>= fun words_len ->
  State.Get.learned_cur_words >> List.length >>= fun learned_words_len ->
  return (percent words_len learned_words_len)
```

This way the getter looks a bit different, but now the body of *progress* getter will trigger only if *words_len* or *learned_words_len* will change. Great!

This concept lies in between things like **reactive programming**, **incremental** and **lenses**, but it focuses only on getting data, it can perform calculations along the way, and it does nothing, unless you ask for it.

**Interesting points**
- Since composition is performed using function monad, we have no limits in the number of arguments
- Memo can accept the optional argument, to customize memoization strategy
- By default *bind* (>>=) function does not perform memoization, so we can apply different features to binding functions. Memoization is only an option

## Things I've learned [in the latest app](https://github.com/MikeSkoe/flash-task)
* Hide types and expose setters/getters.
It will help you to reason about your structure consistency and will allow you to compose multiple getters or setters
* Try to fill your state only with the data you need right now.
OCaml has variant types, it can help you to describe all possible scenarios and what data they will need
* Cyclic dependencies can hurt your architecture
OCaml and dune will warn you if you have cyclic dependencies. Sometimes it is so easy to fix, but tree-like dependencies will make your code more reusable, testable, and pure.