[Article] From object-oriented JavaScript to functional ReScript

Hi everyone! Let me share a new article about ReScript.

I doubt it will tell something new for people here, on the forum. It’s just basics. However, I’d be happy if you use it to introduce someone to ReScript and functional programming in general. I’ll think the mission of this blog post is completed if it helps with converting at least a single developer from JS/TS to ReScript.

21 Likes

This is a fantastic article and really well written! Thank you for taking the time to make it. It’s very helpful to me coming from typescript to see these examples of how to express familiar patterns in a more functional style.

What do you think of the usefulness of functors for expressing shared behavior? As a newcomer with a lot of TS and some Rust experience the docs about functors read almost exactly like those for rust traits. Are functors essentially usable as traits?

In learning rust I found the trait pattern to be incredibly powerful. Being able to extend types with behaviors that then make them usable as inputs for many other functions is almost magical. It seems that rescript has the capability to express a similar pattern but I wonder how useful that is in practice since it doesn’t appear to be “core” to the language the way traits are in rust.

Thanks for the kind words!

TBH, in my production projects, I have tried using first-class modules to express an abstract interface but quickly fallen back to something simpler: passing a curried high-order function as an argument, choosing a required function directly with switch, etc. The reason is that I find the latter much easier to test. Perhaps, I’ve never faced a really complex scenario where a simpler approach really does not scale.

The case I find functors really useful is to provide a “default” implementation for functions of a module based on its few basic functions. Something like:


module NumericCurve = (
  T: {
    type t
    let tesselate: (t, int) => array<Vector2.t>
  },
) => {
  // Bounding box
  let bbox = curve => {
    curve->T.tesselate(50)->BBox.fromVectors
  }

  // Length along the path
  let length = curve => {
    let points = curve->T.tesselate(50)
    let segments = Array.zip(points, points->Js.Array2.sliceFrom(1))
    segments->Array.reduce(0.0, (acc, (va, vb)) => acc +. Vector2.sub(vb, va)->Vector2.length)
  }
}

module CubicBezier = {
  type t = {
    v0: Vector2.t,
    v1: Vector2.t,
    v2: Vector2.t,
    v3: Vector2.t,
  }

  // ...

  let tesselate = divisions => {
    // ... math to extract array of points
  }

  // provide default implementation for `bbox` and `length`
  include NumericCurve({
    type t = t
    let tesselate = tesselate
  })
}

module QuadraticBezier = {
  type t = {
    v0: Vector2.t,
    v1: Vector2.t,
    v2: Vector2.t,
  }

  // ...

  let tesselate = divisions => {
    // ... another math to extract array of points
  }

  // provide default implementation for `bbox` and `length`
  include NumericCurve({
    type t = t
    let tesselate = tesselate
  })
}

// The same for other kinds of curves: ellipses, b-splines, etc

2 Likes