Logo

dev-resources.site

for different kinds of informations.

Reselect, but in OCaml

Published at
10/19/2021
Categories
ocaml
redux
monad
memoization
Author
mikeskoe
Categories
4 categories in total
ocaml
open
redux
open
monad
open
memoization
open
Author
8 person written this
mikeskoe
open
Reselect, but in OCaml

The post was originally written here, so you can go there to play with the code

In this post, I'd like to share with you my implementation of 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.

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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

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

  • 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.
memoization Article's
30 articles in total
Favicon
The intricacies of implementing memoization in Ruby
Favicon
Memorização em JavaScript
Favicon
When do (and don’t) children re-render in React?
Favicon
Memoization in React: When It Helps and When It Hurts
Favicon
Never call the same function twice (with memoization)
Favicon
Optimizing React Performance with Memoization and React.memo
Favicon
Optimizing React Component Performance with Memoization and React.memo
Favicon
Optimizing React Performance with memoization and React.memo
Favicon
Optimizing React Performance with Memoization and React.memo
Favicon
Optimizing React Component Performance with Memoization
Favicon
Кеширования в React - все ли так однозначно?
Favicon
Understanding and Implementing Memoization in React
Favicon
Caching & Memoization with state variables
Favicon
How to implement Memoization in your React projects
Favicon
Memoization in JavaScript
Favicon
Mastering React: A Deep Dive into Memoization and Component Optimization
Favicon
How to Use Memoization in React for Better Performance
Favicon
Deep Dive into Functional Programming in Javascript
Favicon
Demystifying React Memoization: Understanding React.memo() and the useMemo hook
Favicon
JavaScript Memoization
Favicon
Maximizing Performance: How to Memoize Async Functions in JavaScript
Favicon
Retos JS. Calculando factoriales muy grandes con JS.
Favicon
Memoizing DataFrame Functions: Using Hashable DataFrames and Message Digests to Optimize Repeated Calculations
Favicon
Memoize a React component
Favicon
Memoization in JavaScript and React
Favicon
Memoization in Ruby
Favicon
Advent of code Day 21
Favicon
Understanding Memoization in Javascript
Favicon
Reselect, but in OCaml
Favicon
Memoization in Javascript

Featured ones: