Skip to content

Making values reducible

Raynos edited this page Jan 16, 2013 · 3 revisions

This document explains how custom values can be made reducible. Custom because library already does this for all the JS built-in types.

In order to make value reducible (and there for make it compatible with all the reducer functions) one needs to define how internal reduce method works with the value. Currently there are two ways to do that, define reduce for the specific Type / Class so that instances of it will be reducible or just create a fresh reducible value.

Make reducible value

The core of this library is defined in a separate library reducible (https://github.com/Gozala/reducible), which exports a same named function reducible allowing creation of reducible values:

var reducible = require("reducible/reducible")
var input = reducible(function reduceInput(next, initial) {
   // ...
})

Whenever input or any of it's transformation is fold-ed function reduceInput passed to reducible is invoked. It is passed two arguments:

  1. next - function that must be passed each item of the defined input and accumulated state, later is just a value return by next in a previous call.
  2. initial - value representing initial state to start accumulation from. This is used in a first call to next where no state is accumulated yet.
var reducible = require("reducible/reducible")
var input = reducible(function reduceInput(next, initial) {
   var state = initial
   state = next(1, state)
   state = next(2, state)
   // ...
})

End of input

If input is finite then it has an end that must be indicated by passing special end value to a next value. It's analogue to EOF. Once end is passed, input must not call next anymore. All subsequent calls to next will be ignored and warnings will be logged.

var reducible = require("reducible/reducible")
var end = require("reducible/end")
var input = reducible(function reduceInput(next, initial) {
   var state = initial
   state = next(1, state)
   state = next(2, state)
   next(end, state)
})

var print = require("reducers/debug/print")
print(input)
// => < 1 2 >

Errors

Unfortunately some inputs may error, for example representing content of non-existing file should indicate an error. In "reducible" protocol errors can be indicating by passing item that is instance of Error. Errors in the reducible protocol are are equivalent of end input with error. There for same rules apply, once Error instance is passed, input must not call next anymore. All subsequent calls to next will be ignored and warnings will be logged.

var reducible = require("reducible/reducible")
var end = require("reducible/end")
var input = reducible(function reduceInput(next, initial) {
   var state = initial
   state = next(1, state)
   state = next(2, state)
   next(Error("Ooops"), state)
})

var print = require("reducers/debug/print")
print(input)
// => < 1 2 ⚡ [Error: Ooops] >

Interruption

Consumption of input may be interrupted by a consumer, for example input may be consumed until specific item is discovered. To indicate interruption of an input consumption, consumer returns specially boxed value through return value of next. If next returns value such that isReduced(state) is true input must stop by ending a stream, which means either sending end of input: next(end, state.value) or an error: next(Error("Failed to close"), state.value) and next must not be called anymore. All subsequent calls to next will be ignored. Attempt to send value instead of ending an input will cause Error("Failed to interrupt input") to be send to a consumer, indicating error in input:

var reducible = require("reducible/reducible")
var isReduced = require("reducible/is-reduced")
var end = require("reducible/end")
var input = reducible(function reduceInput(next, initial) {
   var state = initial
   state = next(1, state)
   if (isReduced(state)) return next(end, state.value)
   state = next(2, state)
   if (isReduced(state)) return next(end, state.value)
   next(end, state)
})

var print = require("reducers/debug/print")
print(input)
// => < 1 2 >
var take = require("reducers/take")
print(take(input, 1))
// => < 1 >

Make types / class instances reducible

Types can be made reducible in a very similar way as reducible values can be created. In order to define make values of Foo type reducible internal reduce function must be defined as follows:

var reduce = require("reducible/reduce")
reduce.define(Foo, function(foo, next, initial) {
  // ...
})

As you can see API is almost identical to previously described, only difference is additional first foo argument that will be an actual instance being reduced / folded.

Make pre-existing instances reducible

Sometimes one may need to make specific instance reducible instead of making creating new one or making whole type / class reducible. This can also be done in a very similar way as with types only thing that changes is that reduce.implement must be used instead of reduce.define:

var reduce = require("reducible/reduce")
reduce.implement(instance, function(instance, next, initial) {
  // ...
})

Examples

Now go ahead and make things reducible!! In the process you may find some already existing libraries useful to look at: