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:

```
let circleCurve = curve.describe(
curve.x(
curve.hasFrequency(1/periodInMilliseconds),
curve.hasAmplitude(1),
curve.hasKernel(math.cosine)
),
curve.y(
curve.hasFrequency(1/periodInMilliseconds),
curve.hasAmplitude(1),
curve.hasKernel(math.sine)
)
);
console.log(circleCurve(0).x) // 1
```

## 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:

```
const applyToDim = (dim) => R.compose(R.over(R.lensProp(dim)), R.compose);
export const x = applyToDim('x');
const setter = R.curry((lens, val) => R.set(lens, val));
const frequency = R.lensProp('frequency');
export const hasFrequency = setter(frequency)
```

The code above could cause some confusion as `R.compose`

does right-to-left function application, i.e. `R.compose(f, 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:

```
let lissajousCurve = curve.describe(
curve.hasFrequency('x', 1/periodInMilliseconds),
curve.hasFrequency('y', 2/periodInMilliseconds),
curve.hasAmplitude('x', 1),
curve.hasAmplitude('y', 1),
curve.hasPhase('x', math.TAU/4)
);
```

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

- Most importantly, it allows the user to generate invalid input, e.g.
`curve.hasAmplitude('thisMakesNoSense', 1)`

- 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.

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.

## Here’s the code

```
let lissajousSizeCurve = curve.describeCurve(
curve.x(
curve.hasFrequency(1/periodInMilliseconds),
curve.hasAmplitude(1),
curve.hasKernel(math.cosine),
curve.hasPadding(0.1)
),
curve.y(
curve.hasFrequency(2/periodInMilliseconds),
curve.hasAmplitude(1),
curve.hasKernel(math.sine),
curve.hasPadding(0.1)
),
curve.z(
curve.hasFrequency(0.25/periodInMilliseconds),
curve.hasAmplitude(0.1),
curve.hasKernel(math.sine)
)
);
```