What
This is a port of the Haskell Control.Foldl package.
This package provides efficient left folds in applictive styles.
fantasy-land compatible.
e.g. For calculating standard deviation using the formular below.
const varianceFormular = Math
We can fold an array using this package's utility functions.
// generate a foldconst std = sqrSum const arr = 1 2 // actually run the fold by calling reduceconst result = std // 0.5
Why
It's very easy to write inefficient folds when multiple foldings are needed.
e.g. here is a naive implementation of calculating standard deviation:
const arr = 1 2 const sqrSum = arr const length = arr const mean = arr / length const std = lengthmean
The code above loop through the array every time reduce is called. Whereas, the example in the first section only loops once.
It's also very easy to write efficient folds but not generic enough. e.g.
const sqrSum length sum = arr1 2 const std = lengthsum / length
The above code increase effeciency as in calculating every step of square sum, sum and length at the same place. Accordingly, it combines the starting values for each operation (square sum, sum and length) inside an array as the initial value to the reduce function. This approach can be easilly adopted for combining any number of foldings. This package is to provide abstraction over this approach and utility funcitons for various foldings for your needs.
How
Compose existing folds
To come up a new fold base on existing folds (in this example calculating average of an array of numbers), first, write the algorithm in a curried function.
// instead of (sum, length) => ...const avgAlgorithm = sum / length
Second, use map
and ap
to generate a fold
const avg = sum // since avgAlgorithm take the sum of array first, we put sum here first // we use map to 'embed' avgAlgorithm into fold. // we use `ap` to bring the length of the array to the second argument of avgAlgorithm// the above does not actually doing any folding but instead constructing the folding like the example under `Why` section to be triggered.
Thirdly, run the fold using reduce
.
avg // 0.5
Write your own folds
If none of the build-in utils satisfy your needs, you can always write your own (PR welcome):
const halfSum = halfSum // 2
Pre-operations
Map before fold is a very common need. But a separate map from fold is not efficient. This package provides preMap for this.
const sqr = a * a const sqrSum = sum// [1, 2] will only be looped oncesqrSum // 5
Similarly, there is a prefilter to combine a filter operation with fold efficiently.
const sumOver2 = sum// [1, 2, 3, 4] will only be looped once for summing and filtering at the same timesumOver2 // 7
The Functor instance
The above example can be written like this:
const halfSum = sum halfSum // 2
This is because any fold (the sum
imported in this case) is a functor, it maps the last param passed to Fold
when constrcted (in sum
, it is id
). And id
as a function is a functor itself as well. So maping on it is the same as compose
a function on to id.
The monadic(generalized) version
This package also provide a monadic version of the Fold
called FoldM
.
// create a Monad called AMonadconst AMonad = daggyAMonad AMonadprototype { return }AMonadprototype { return } // make array a fantasy-land compatible MonoidArray // this is in the source code:const sink = const result = AMonad result // AMonad([2, 4, 6, 3, 5, 7, 4, 6, 8])
There are also monadic versions for premap and prefilter
Convert to monadic version
Sometime, you need to use the build-in non-monadic util together with FoldM
. Then you need to convert them.
const sumM = sum sumM // SomeMonad(6)
You can use map
and ap
just like before
const g = const sumM = const lengthM = const avg = sum / length sumM // AMonad(5)
API documentation
Type signaure, description and examples are in source code.