Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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.
- Host: GitHub
- URL: https://github.com/puffnfresh/bilby.js
- Owner: puffnfresh
- License: mit
- Created: 2012-09-08T02:11:24.000Z (about 12 years ago)
- Default Branch: master
- Last Pushed: 2014-08-20T09:29:15.000Z (about 10 years ago)
- Last Synced: 2024-07-23T23:02:41.201Z (2 months ago)
- Language: JavaScript
- Homepage: http://bilby.brianmckenna.org/
- Size: 656 KB
- Stars: 592
- Watchers: 34
- Forks: 24
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
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`.