Experiments with declarative API's and Ramda

April 26, 2016 by Jeroen

Last weekend I spend some time trying Ramda, a functional programming library for JavaScript. I decided to build a small declarative API to create parametric curves from trigonometric functions. In the end, the entry-point to the library is one variadic function, describe. The function takes as arguments a description of the curve rather than instructions on how to construct the curve.

describe then takes these descriptions to generate one unary function of time that calculates the coordinates of the curve. A possible description for a curve that traverses the circumference of a circle is:

Implementation Details

The implementation of the curve-module revolves around Lenses. The descriptive functions, hasFrequency, hasAmplitude etc, are partially applied setter functions. Functions x, y and z, then select the relevant part of the internal data structure and apply the setters to it, using R.over.

This code shows how curve.x and curve.hasFrequency are currently implemented:

The code above could cause some confusion as R.compose does right-to-left function application, i.e. R.compose(f, g) = f \cdot{} g. This means that if a user were to, say, specify the frequency twice, the first would prevail. There also is R.pipe that does left-to-right function composition. I decided not to worry about this as specifying two different frequencies leads to a contradictory and therefore incorrect specification.

How not to do it

The trick to being fully declarative is to shield the user of the library from any implementation details. The following code is from an earlier iteration:

The fact that ‘x’, ‘y’ and ‘z’ have to be passed as strings is bad for two reasons:

  1. Most importantly, it allows the user to generate invalid input, e.g. curve.hasAmplitude('thisMakesNoSense', 1)
  2. Secondly, it leaks an implementation detail, that the different parametric equations are some how stored in a dictionary-like structure.

This API places an additional burden on the maintainer as the first argument has to be validated. This requires an error-handling strategy. Worse – even though this is irrelevant to JavaScript libraries – is that such errors can only be detected and handled at runtime.

Final thoughts on Ramda

While we are on the subject of handling errors at runtime… In JavaScript programming, logic and typographical errors only surface while code is executing. And it turns out that, when something goes wrong, Ramda makes debugging very difficult! The flow of an application is obfuscated by additional layers of code that enable a functional programming style. The image below illustrates how a stacktrace is affected.

Typical stacktrace in Ramda
Typical stacktrace in Ramda

The Ramda developers do a notable job in bringing functional programming concepts to pure JavaScript applications. However, if you are serious about functional programming for the browser, a language such as PureScript, Elm or ClojureScript. The first two have rich type systems that can catch many errors at compile time.

Lissajous curve with parameterized size
Lissajous curve with parameterized size

Here’s the code