Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/puffnfresh/bilby.js

Serious functional programming library for JavaScript.
https://github.com/puffnfresh/bilby.js

Last synced: about 2 months ago
JSON representation

Serious functional programming library for JavaScript.

Awesome Lists containing this project

README

        

% bilby.js

![](http://brianmckenna.org/files/bilby.png)

# Build status

Main
[![Build Status](https://secure.travis-ci.org/puffnfresh/bilby.js.png)](http://travis-ci.org/puffnfresh/bilby.js)

Dependency
[![Dependencies](https://david-dm.org/SimonRichardson/bilby.js.png)](https://david-dm.org/SimonRichardson/bilby.js)

# Description

bilby.js is a serious functional programming library. Serious,
meaning it applies category theory to enable highly abstract
and generalised code. Functional, meaning that it enables
referentially transparent programs.

Some features include:

* Immutable multimethods for ad-hoc polymorphism
* Functional data structures
* Automated specification testing (ScalaCheck, QuickCheck)
* Fantasy Land compatible

![](https://raw.github.com/puffnfresh/fantasy-land/master/logo.png)

# Usage

node.js:

var bilby = require('bilby');

Browser:

# Development

Download the code with git:

git clone https://github.com/puffnfresh/bilby.js.git

Install the development dependencies with npm:

npm install

Run the tests with grunt:

npm test

Build the concatenated scripts with grunt:

$(npm bin)/grunt

Generate the documentation with emu:

$(npm bin)/emu < bilby.js

# Environment

Environments are very important in bilby. The library itself is
implemented as a single environment.

An environment holds methods and properties.

Methods are implemented as multimethods, which allow a form of
*ad-hoc polymorphism*. Duck typing is another example of ad-hoc
polymorphism but only allows a single implementation at a time, via
prototype mutation.

A method instance is a product of a name, a predicate and an
implementation:

var env = bilby.environment()
.method(
// Name
'negate',
// Predicate
function(n) {
return typeof n == 'number';
},
// Implementation
function(n) {
return -n;
}
);

env.negate(100) == -100;

We can now override the environment with some more implementations:

var env2 = env
.method(
'negate',
function(b) {
return typeof b == 'boolean';
},
function(b) {
return !b;
}
);

env2.negate(100) == -100;
env2.negate(true) == false;

The environments are immutable; references to `env` won't see an
implementation for boolean. The `env2` environment could have
overwritten the implementation for number and code relying on `env`
would still work.

Properties can be accessed without dispatching on arguments. They
can almost be thought of as methods with predicates that always
return true:

var env = bilby.environment()
.property('name', 'Brian');

env.name == 'Brian';

This means that bilby's methods can be extended:

function MyData(data) {
this.data = data;
}

var _ = bilby.method(
'equal',
bilby.isInstanceOf(MyData),
function(a, b) {
return this.equal(a.data, b.data);
}
);

_.equal(
new MyData(1),
new MyData(1)
) == true;

_.equal(
new MyData(1),
new MyData(2)
) == false;

## environment(methods = {}, properties = {})

* method(name, predicate, f) - adds an multimethod implementation
* property(name, value) - sets a property to value
* envConcat(extraMethods, extraProperties) - adds methods + properties
* envAppend(e) - combines two environemts, biased to `e`

# Helpers

The helpers module is a collection of functions used often inside
of bilby.js or are generally useful for programs.

## functionName(f)

Returns the name of function `f`.

## functionLength(f)

Returns the arity of function `f`.

## bind(f)(o)

Makes `this` inside of `f` equal to `o`:

bilby.bind(function() { return this; })(a)() == a

Also partially applies arguments:

bilby.bind(bilby.add)(null, 10)(32) == 42

## curry(f)

Takes a normal function `f` and allows partial application of its
named arguments:

var add = bilby.curry(function(a, b) {
return a + b;
}),
add15 = add(15);

add15(27) == 42;

Retains ability of complete application by calling the function
when enough arguments are filled:

add(15, 27) == 42;

## flip(f)

Flips the order of arguments to `f`:

var concat = bilby.curry(function(a, b) {
return a + b;
}),
prepend = flip(concat);

## identity(o)

Identity function. Returns `o`:

forall a. identity(a) == a

## constant(c)

Constant function. Creates a function that always returns `c`, no
matter the argument:

forall a b. constant(a)(b) == a

## compose(f, g)

Creates a new function that applies `f` to the result of `g` of the
input argument:

forall f g x. compose(f, g)(x) == f(g(x))

## create(proto)

Partial polyfill for Object.create - creates a new instance of the
given prototype.

## getInstance(self, constructor)

Always returns an instance of constructor.

Returns self if it is an instanceof constructor, otherwise
constructs an object with the correct prototype.

## tagged(name, fields)

Creates a simple constructor for a tagged object.

var Tuple = tagged('Tuple', ['a', 'b']);
var x = Tuple(1, 2);
var y = new Tuple(3, 4);
x instanceof Tuple && y instanceof Tuple;

## taggedSum(constructors)

Creates a disjoint union of constructors, with a catamorphism.

var List = taggedSum({
Cons: ['car', 'cdr'],
Nil: []
});
function listLength(l) {
return l.cata({
Cons: function(car, cdr) {
return 1 + listLength(cdr);
},
Nil: function() {
return 0;
}
});
}
listLength(List.Cons(1, new List.Cons(2, List.Nil()))) == 2;

## error(s)

Turns the `throw new Error(s)` statement into an expression.

## zip(a, b)

Takes two lists and pairs their values together into a "tuple" (2
length list):

zip([1, 2, 3], [4, 5, 6]) == [[1, 4], [2, 5], [3, 6]]

## singleton(k, v)

Creates a new single object using `k` as the key and `v` as the
value. Useful for creating arbitrary keyed objects without
mutation:

singleton(['Hello', 'world'].join(' '), 42) == {'Hello world': 42}

## extend(a, b)

Right-biased key-value concat of objects `a` and `b`:

bilby.extend({a: 1, b: 2}, {b: true, c: false}) == {a: 1, b: true, c: false}

## isTypeOf(s)(o)

Returns `true` iff `o` has `typeof s`.

## isFunction(a)

Returns `true` iff `a` is a `Function`.

## isBoolean(a)

Returns `true` iff `a` is a `Boolean`.

## isNumber(a)

Returns `true` iff `a` is a `Number`.

## isString(a)

Returns `true` iff `a` is a `String`.

## isArray(a)

Returns `true` iff `a` is an `Array`.

## isEven(a)

Returns `true` iff `a` is even.

## isOdd(a)

Returns `true` iff `a` is odd.

## isInstanceOf(c)(o)

Returns `true` iff `o` is an instance of `c`.

## AnyVal

Sentinal value for when any type of primitive value is needed.

## Char

Sentinal value for when a single character string is needed.

## arrayOf(type)

Sentinel value for when an array of a particular type is needed:

arrayOf(Number)

## isArrayOf(a)

Returns `true` iff `a` is an instance of `arrayOf`.

## objectLike(props)

Sentinal value for when an object with specified properties is
needed:

objectLike({
age: Number,
name: String
})

## isObjectLike(a)

Returns `true` iff `a` is an instance of `objectLike`.

## or(a)(b)

Curried function for `||`.

## and(a)(b)

Curried function for `&&`.

## add(a)(b)

Curried function for `+`.

## strictEquals(a)(b)

Curried function for `===`.

## not(a)

Returns `true` iff `a` is falsy.

## fill(s)(t)

Curried function for filling array.

## range(a, b)

Create an array with a given range (length).

## liftA2(f, a, b)

Lifts a curried, binary function `f` into the applicative passes
`a` and `b` as parameters.

## sequence(m, a)

Sequences an array, `a`, of values belonging to the `m` monad:

bilby.sequence(Array, [
[1, 2],
[3],
[4, 5]
]) == [
[1, 3, 4],
[1, 3, 5],
[2, 3, 4],
[2, 3, 5]
]

# Do (operator overloading)

Adds operator overloading for functional syntax:

* `>=` - monad flatMap/bind:

bilby.Do()(
bilby.some(1) >= function(x) {
return x < 0 ? bilby.none : bilby.some(x + 2);
}
).getOrElse(0) == 3;

* `>>` - kleisli:

bilby.Do()(
function(x) {
return x < 0 ? bilby.none : bilby.some(x + 1);
} >> function(x) {
return x % 2 != 0 ? bilby.none : bilby.some(x + 1);
}
)(1).getOrElse(0) == 3;

* `<` - functor map:

bilby.Do()(
bilby.some(1) < add(2)
).getOrElse(0) == 3;

* `*` - applicative ap(ply):

bilby.Do()(
bilby.some(add) * bilby.some(1) * bilby.some(2)
).getOrElse(0) == 3;

* `+` - semigroup concat:

bilby.Do()(
bilby.some(1) + bilby.some(2)
).getOrElse(0) == 3;

## Do()(a)

Creates a new syntax scope. The `a` expression is allowed multiple
usages of a single operator per `Do` call:

* `>=` - flatMap
* `>>` - kleisli
* `<` - map
* `*` - ap
* `+` - concat

The associated name will be called on the bilby environment with
the operands. For example:

bilby.Do()(bilby.some(1) + bilby.some(2))

Desugars into:

bilby.concat(bilby.some(1), bilby.some(2))

## Do.setValueOf(proto)

Used to mutate the `valueOf` property on `proto`. Necessary to do
the `Do` block's operator overloading. Uses the object's existing
`valueOf` if not in a `Do` block.

*Warning:* this mutates `proto`. May not be safe, even though it
tries to default back to the normal behaviour when not in a `Do`
block.

# Trampoline

Reifies continutations onto the heap, rather than the stack. Allows
efficient tail calls.

Example usage:

function loop(n) {
function inner(i) {
if(i == n) return bilby.done(n);
return bilby.cont(function() {
return inner(i + 1);
});
}

return bilby.trampoline(inner(0));
}

Where `loop` is the identity function for positive numbers. Without
trampolining, this function would take `n` stack frames.

## done(result)

Result constructor for a continuation.

## cont(thunk)

Continuation constructor. `thunk` is a nullary closure, resulting
in a `done` or a `cont`.

## trampoline(bounce)

The beginning of the continuation to call. Will repeatedly evaluate
`cont` thunks until it gets to a `done` value.

# Id

* concat(b) - semigroup concat
* map(f) - functor map
* ap(b) - applicative ap(ply)
* chain(f) - chain value
* arb() - arbitrary value

## isId(a)

Returns `true` if `a` is `Id`.

## idOf(type)

Sentinel value for when an Id of a particular type is needed:

idOf(Number)

## isIdOf(a)

Returns `true` iff `a` is an instance of `idOf`.

# Option

Option a = Some a + None

The option type encodes the presence and absence of a value. The
`some` constructor represents a value and `none` represents the
absence.

* fold(a, b) - applies `a` to value if `some` or defaults to `b`
* getOrElse(a) - default value for `none`
* isSome - `true` iff `this` is `some`
* isNone - `true` iff `this` is `none`
* toLeft(r) - `left(x)` if `some(x)`, `right(r)` if none
* toRight(l) - `right(x)` if `some(x)`, `left(l)` if none
* flatMap(f) - monadic flatMap/bind
* map(f) - functor map
* ap(s) - applicative ap(ply)
* concat(s, plus) - semigroup concat

## of(x)

Constructor `of` Monad creating `Option` with value of `x`.

## some(x)

Constructor to represent the existence of a value, `x`.

## none

Represents the absence of a value.

## isOption(a)

Returns `true` if `a` is a `some` or `none`.

# Either

Either a b = Left a + Right b

Represents a tagged disjunction between two sets of values; `a` or
`b`. Methods are right-biased.

* fold(a, b) - `a` applied to value if `left`, `b` if `right`
* swap() - turns `left` into `right` and vice-versa
* isLeft - `true` iff `this` is `left`
* isRight - `true` iff `this` is `right`
* toOption() - `none` if `left`, `some` value of `right`
* toArray() - `[]` if `left`, singleton value if `right`
* flatMap(f) - monadic flatMap/bind
* map(f) - functor map
* ap(s) - applicative ap(ply)
* concat(s, plus) - semigroup concat

## left(x)

Constructor to represent the left case.

## right(x)

Constructor to represent the (biased) right case.

## isEither(a)

Returns `true` iff `a` is a `left` or a `right`.

# Validation

Validation e v = Failure e + Success v

The Validation data type represents a "success" value or a
semigroup of "failure" values. Validation has an applicative
functor which collects failures' errors or creates a new success
value.

Here's an example function which validates a String:

function nonEmpty(field, string) {
return string
? λ.success(string)
: λ.failure([field + " must be non-empty"]);
}

We might want to give back a full-name from a first-name and
last-name if both given were non-empty:

function getWholeName(firstName) {
return function(lastName) {
return firstName + " " + lastName;
}
}
λ.ap(
λ.map(nonEmpty("First-name", firstName), getWholeName),
nonEmpty("Last-name", lastName)
);

When given a non-empty `firstName` ("Brian") and `lastName`
("McKenna"):

λ.success("Brian McKenna");

If given only an invalid `firstname`:

λ.failure(['First-name must be non-empty']);

If both values are invalid:

λ.failure([
'First-name must be non-empty',
'Last-name must be non-empty'
]);

* map(f) - functor map
* ap(b, concat) - applicative ap(ply)

## success(value)

Represents a successful `value`.

## failure(errors)

Represents a failure.

`errors` **must** be a semigroup (i.e. have an `concat`
implementation in the environment).

## success(x)

Constructor to represent the existance of a value, `x`.

## failure(x)

Constructor to represent the existance of a value, `x`.

## isValidation(a)

Returns `true` iff `a` is a `success` or a `failure`.

# Lenses

Lenses allow immutable updating of nested data structures.

## store(setter, getter)

A `store` is a combined getter and setter that can be composed with
other stores.

## isStore(a)

Returns `true` iff `a` is a `store`.

## lens(f)

A total `lens` takes a function, `f`, which itself takes a value
and returns a `store`.

* run(x) - gets the lens' `store` from `x`
* compose(l) - lens composition

## isLens(a)

Returns `true` iff `a` is a `lens`.

## objectLens(k)

Creates a total `lens` over an object for the `k` key.

# Input/output

Purely functional IO wrapper.

## io(f)

Pure wrapper around a side-effecting `f` function.

* perform() - action to be called a single time per program
* flatMap(f) - monadic flatMap/bind

## isIO(a)

Returns `true` iff `a` is an `io`.

# Tuples

Tuples are another way of storing multiple values in a single value.
They have a fixed number of elements (immutable), and so you can't
cons to a tuple.
Elements of a tuple do not need to be all of the same type

Example usage:

bilby.Tuple2(1, 2);
bilby.Tuple3(1, 2, 3);
bilby.Tuple4(1, 2, 3, 4);
bilby.Tuple5(1, 2, 3, 4, 5);

* arb() - arbitrary value

## Tuple2

* flip() - flip values
* concat() - Semigroup (value must also be a Semigroup)
* map() - functor map

## Tuple3

* concat() - Semigroup (value must also be a Semigroup)
* map() - functor map

## Tuple4

* concat() - Semigroup (value must also be a Semigroup)
* map() - functor map

## Tuple5

* concat() - Semigroup (value must also be a Semigroup)
* map() - functor map

## isTuple2(a)

Returns `true` if `a` is `Tuple2`.

## isTuple4(a)

Returns `true` if `a` is `Tuple3`.

## isTuple4(a)

Returns `true` if `a` is `Tuple4`.

## isTuple5(a)

Returns `true` if `a` is `Tuple5`.

# Promise(fork)

Promise is a constructor which takes a `fork` function. The `fork`
function takes one argument:

fork(resolve)

Where `resolve` is a side-effecting callback.

## fork(resolve)

The `resolve` callback gets called when a value is resolved.

## of(x)

Creates a Promise that contains a successful value.

## chain(f)

Returns a new promise that evaluates `f` when the current promise
is successfully fulfilled. `f` must return a new promise.

## map(f)

Returns a new promise that evaluates `f` on a value and passes it
through to the resolve function.

## isPromise(a)

Returns `true` if `a` is `Promise`.

# State(run)

* chain() - TODO
* evalState() - evaluate state
* execState() - execute on state
* map() - functor map
* ap() - applicative ap(ply)

## isState(a)

Returns `true` if `a` is `State`.

# List

List a = Cons a + Nil

The list type data type constructs objects which points to values. The `cons`
constructor represents a value, the left is the head (`car`, the first element)
and the right represents the tail (`cdr`, the second element). The `nil`
constructor is defined as an empty list.

The following example creates a list of values 1 and 2, where the nil terminates
the list:

cons(1, cons(2, nil));

The following can also represent tree like structures (Binary Trees):

cons(cons(1, cons(2, nil)), cons(3, cons(4, nil)));

*
/ \
* *
/ \ / \
1 2 3 4

* concat(a) - semigroup concat
* fold(a, b) - applies `a` to value if `cons` or defaults to `b`
* map(f) - functor map
* fold(f) - applies f to values
* flatMap(f) - monadic flatMap
* append(a) - append
* appendAll(a) - append values
* prepend(a) - prepend value
* prependAll(a) - prepend values
* reverse() - reverse
* exists() - test by predicate
* filter() - filter by predicate
* partition() - partition by predicate
* size() - size of the list

## cons(a, b)

Constructor to represent the existence of a value in a list, `a`
and a reference to another `b`.

## nil

Represents an empty list (absence of a list).

## isList(a)

Returns `true` if `a` is a `cons` or `nil`.

# QuickCheck

QuickCheck is a form of *automated specification testing*. Instead
of manually writing tests cases like so:

assert(0 + 1 == 1);
assert(1 + 1 == 2);
assert(3 + 3 == 6);

We can just write the assertion algebraicly and tell QuickCheck to
automaticaly generate lots of inputs:

bilby.forAll(
function(n) {
return n + n == 2 * n;
},
[Number]
).fold(
function(fail) {
return "Failed after " + fail.tries + " tries: " + fail.inputs.toString();
},
"All tests passed!"
)

### failureReporter

* inputs - the arguments to the property that failed
* tries - number of times inputs were tested before failure

## forAll(property, args)

Generates values for each type in `args` using `bilby.arb` and
then passes them to `property`, a function returning a
`Boolean`. Tries `goal` number of times or until failure.

Returns an `Option` of a `failureReporter`:

var reporter = bilby.forAll(
function(s) {
return isPalindrome(s + s.split('').reverse().join(''));
},
[String]
);

## goal

The number of successful inputs necessary to declare the whole
property a success:

var _ = bilby.property('goal', 1000);

Default is `100`.