Functions

Import functions from the Fn module:

import { get, matches } from '/fn/module.js';

Functions

cache(fn)

Returns a function that caches the output values of fn(input) against input values in a map, such that for each input value fn is only ever called once.

choose(fn, map)

Returns a function that takes its first argument as a key and uses it to select a function in map which is invoked with the remaining arguments.

Where map has a function default, that function is run when a key is not found, otherwise unfound keys will error.

var fn = choose({
    'fish':  function fn1(a, b) {...},
    'chips': function fn2(a, b) {...}
});

fn('fish', a, b);   // Calls fn1(a, b)

overload(fn, map)

Returns a function that calls a function at the property of object that matches the result of calling fn with all arguments.

var fn = overload(toType, {
    string: function a(name, n) {...},
    number: function b(n, m) {...}
});

fn('pie', 4); // Returns a('pie', 4)
fn(1, 2);     // Returns b(1, 2)

weakCache(fn)

Returns a function that caches the return values of fn() against input values in a WeakMap, such that for each input value fn is only ever called once.

The functions below are curried (where they take more than one parameter), allowing them to be partially applied.

Objects

argument(n)

Returns a function that returns its nth argument when called.

call(fn)

Returns a function that calls fn() with no arguments.

equals(a, b)

Perform a deep equality comparison of a and b. Returns true if they are equal.

is(a, b)

Perform a strict equality check of a === b.

isDefined(value)

Check for value – where value is undefined, NaN or null, returns false, otherwise true.

Strings

capture(regex, reducers, accumulator, string)

Parse string with regex, calling functions in reducers to modify and return accumulator.

Reducers is an object of functions keyed by the index of their capturing group in the regexp result (0 corresponding to the entire regex match, the first capturing group being at index 1). Reducer functions are called in capture order for all capturing groups that captured something. Reducers may also define the function ‘close’, which is called at the end of every capture. All functions are passed the paremeters (accumulator, tokens), where tokens is the regexp result. Functions must return an accumulator.

Reducers may also define a function 'catch', which is called when a match has not been made (where 'catch' is not defined an error is thrown).

const rvalue = /^\s*(-?\d*\.?\d+)(\w+)?\s*$/;
const parseValue = capture(rvalue, {
    // Create a new accumulator object each call
    0: () => ({}),

    1: (acc, tokens) => {
        acc.number = parseFloat(tokens[1]);
        return acc;
    },

    2: (acc, tokens) => {
        acc.unit = tokens[2];
        return acc;
    }
}, {});

const value = parseValue('36rem');    // { value: 36, unit: 'rem' }

parseDate(date)

Parse a date, where, date may be:

  • a string in ISO date format
  • a number in seconds UNIX time
  • a date object

Returns a date object, or the date object, if it validates.

parseTime(time)

If time is a number it is returned. If it is a string then it is parsed as a time in ISO time format: as hours '13', with minutes '13:25', with seconds '13:25:14' or decimal seconds '13:25:14.001', and returned as a number in seconds.

const time = parseTime('13:25:14.001');   // 48314.001

parseInt(string)

Parse to integer without having to worry about the radix parameter, making it suitable, for example, to use in array.map(parseInt).

Numbers

mod(divisor, n)

JavaScript’s modulu operator (%) uses Euclidean division, but for stuff that cycles through 0 the symmetrics of floored division are often are more useful.

gaussian()

Generate a random number with a bell curve probability centred around 0 with limits -1 to 1.

Arrays

insert(fn, array, object)

Inserts object into array at the first index where the result of fn(object) is greater than fn(array[index]).

unique(array)

Takes an array or stream as array, returns an object of the same type without duplicate values.

update(fn, array, object)

Compares the result of calling fn on object to the result of calling fn on each value in array. If a match is found, object has its properties assigned to that target, and if not the object is spliced into the array (preserving a sort order based on the result of fn(object)).

Returns the updated object.

Time

parseDate(date)

Parse a date, where, date may be:

  • a string in ISO date format
  • a number in seconds UNIX time
  • a date object

Returns a date object, or the date object, if it validates.

parseDateLocal(date)

As parseDate(date), but returns a date object with local time set to the result of the parse (or the original date object, if it validates).

formatDateISO(date)

Formats date (a string or a number or date accepted by parseDate(date)) as a string in the ISO date format.

formatDateTimeISO(date)

Formats date (a string or a number or date accepted by parseDate(date)) as a string in the ISO datetime format.

toDay(date)

Returns day of week as a number, where monday is 0.

addDate(diff, date)

Sums diff and date, where diff is a string in ISO date format. Returns a new date object.

const addWeek = addDate('0000-00-07');
const sameTimeNextWeek = addWeek(new Date());

floorDate(token, date)

Floors date to the nearest token, where token is one of: 'year', 'month', 'week', 'day', 'hour', 'minute' or 'second'; 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'; or a number representing a weekday.

const dayCounts = times.map(floorTime('days'));

formatDate(format, date)

Formats date (a string or number or date accepted by parseDate(date)) to the format of the string format. The format string may contain the tokens:

  • 'YYYY' years
  • 'YY' 2-digit year
  • 'MM' month, 2-digit
  • 'MMM' month, 3-letter
  • 'MMMM' month, full name
  • 'D' day of week
  • 'DD' day of week, two-digit
  • 'ddd' weekday, 3-letter
  • 'dddd' weekday, full name
  • 'hh' hours
  • 'mm' minutes
  • 'ss' seconds
const time = formatTime('+-hh:mm:ss', 3600);   // 01:00:00

parseTime(time)

If time is a number it is returned. If it is a string then it is parsed as a time in ISO time format: as hours '13', with minutes '13:25', with seconds '13:25:14' or decimal seconds '13:25:14.001', and returned as a number in seconds.

const time = parseTime('13:25:14.001');   // 48314.001

formatTime(format, time)

Formats time (a string or a number) to the format of the format string. The format string may contain the tokens:

  • ‘+-‘ sign
  • 'www' weeks
  • 'dd' days
  • 'hhh' duration hours, unlimited
  • 'hh' time hours, 24-hour cycle
  • 'mm' time minutes
  • 'ss' time seconds
  • 'sss' time seconds with decimals
  • 'ms' time milliseconds
const time = formatTime('+-hh:mm:ss', 3600);   // 01:00:00

formatTimeISO(time)

Formats time (a string or a number accepted by parseTime(time)) as a string in the ISO time format. `

addTime(time1, time2)

Sums time2 and time1, returning UNIX time as a number in seconds. If time1 is a string, it is parsed as a time diff, where numbers are accepted outside the bounds of 0-24 hours or 0-60 minutes or seconds. For example, to add 72 minutes to a list of times:

const laters = times.map(addTime('00:72'));

floorTime(token, time)

Floors time to the nearest token, where token is one of: 'week', 'day', 'hour', 'minute' or 'second'. time may be an ISO time string or a time in seconds. Returns a time in seconds.

const hourCounts = times.map(floorTime('hour'));

Observer

Import and create an observer:

import { Observer, observe } from '/fn/module.js';
const data = Observer({ a: true });

An observer is an ES6 Proxy of an object that intercepts and reports mutations. There is only ever one observer per object, ie, calling Observer(object) multiple times with the same object always returns the same observer. Observers can be observed for mutations with the observe function:

observe('a', (value) => {...}, data);

Observer

Observer(object)

Create an Observer proxy around object. In order for observe(...) to detect mutations, changes must be made to this proxy rather than the original object.

observe(path, fn, object [, init])

Observe path in object and call fn(value) with the value at the end of that path when it mutates. Returns a function that destroys this observer.

The callback fn is called immediately on initialisation if the value at the end of the path is not equal to init. In the default case where init is undefined, paths that end in undefined do not cause the callback to be called.

(To force the callback to always be called on setup, pass in NaN as an init value. In JS NaN is not equal to anything (even NaN), so it always initialises.)

Stream

Import and create a stream:

import { Stream } from '/fn/module.js';
const stream = Stream.of(1,2,3,4);

Construct

Stream(fn)

Construct a new stream. The new keyword is optional. fn(notify, stop) is invoked when the stream is started: it must return a source object – a ‘producer’ – with the method .shift() and optionally methods .push(), .start() and .stop().

Stream.from(values)

Returns a writeable stream that consumes the array or array-like values as its source.

Stream.fromPromise(promise)

Returns a stream that uses the given promise as its source. When the promise resolves the stream is given its value and stopped. If the promise errors the stream is stopped without value. This stream is not writeable: it has no .push() method.

Stream.fromTimer(timer)

Create a stream from a timer object. A timer is an object with the properties:

{
    request:     fn(fn), calls fn on the next frame, returns an id
    cancel:      fn(id), cancels request with id
    now:         fn(), returns the time
    currentTime: time at the start of the latest frame
}

Here is how a stream of animation frames may be created:

const frames = Stream.fromTimer({
    request: window.requestAnimationFrame,
    cancel: window.cancelAnimationFrame,
    now: () => window.performance.now()
});

This stream is not writeable: it has no .push() method.

Properties

.status

Reflects the running status of the stream. When all values have been consumed status is 'done'.

Write

.push(value)

Pushes a value (or multiple values) into the head of a writeable stream. If the stream is not writeable, it does not have a .push() method.

Map

.flat()

Flattens a stream of streams or arrays into a single stream.

.flatMap(fn)

Maps values to lists – fn(value) must return an array, functor, stream (or any other duck with a .shift() method) and flattens those lists into a single stream.

.map(fn)

Maps values to the result of fn(value).

.merge(stream)

Merges this stream with stream, which in fact may be an array, array-like or functor.

.scan(fn, seed)

Calls fn(accumulator, value) and emits accumulator for each value in the stream.

Filter

.filter(fn)

Filter values according to the truthiness of fn(value).

.latest()

When the stream has a values buffered, passes the last value in the buffer.

.rest(n)

Filters the stream to the nth value and above.

.take(n)

Filters the stream to the first n values.

.throttle(time)

Throttles values such that the latest value is emitted every time seconds. Other values are discarded. The parameter time may also be a timer options object, an object with { request, cancel, now } functions, allowing the creation of, say, and animation frame throttle.

.wait(time)

Emits the latest value only after time seconds of inactivity. Other values are discarded.

Read

.clone()

Creates a read-only copy of the stream.

.each(fn)

Thirstilly consumes the stream, calling fn(value) whenever a value is available.

.last(fn)

Consumes the stream when stopped, calling fn(value) with the last value read from the stream.

.fold(fn, accumulator)

Consumes the stream when stopped, calling fn(accumulator, value) for each value in the stream. Returns a promise.

.shift()

Reads a value from the stream. If no values are in the stream, returns undefined. If this is the last value in the stream, streams.status is 'done'.

Lifecycle

.done(fn)

Calls fn() after the stream is stopped and all values have been drained.

.start()

If the stream’s producer is startable, starts the stream.

.stop()

Stops the stream. No more values can be pushed to the stream and any consumers added will do nothing. However, depending on the stream’s source the stream may yet drain any buffered values into an existing consumer before entering 'done' state. Once in 'done' state a stream is entirely inert.