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:
The implementation of the curve-module revolves around Lenses. The descriptive functions,
hasAmplitude etc, are partially applied setter functions. Functions
z, then select the relevant part of the internal data structure and apply the setters to it, using
This code shows how
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) = . 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:
- Most importantly, it allows the user to generate invalid input, e.g.
- Secondly, it leaks an implementation detail, that the different parametric equations are some how stored in a dictionary-like structure.
Final thoughts on Ramda
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) ) );