Lambdash is a library for generic functional programming in JavaScript.
Lamdash enables you to create custom data types that are on equal footing with the types built into the javascript language. It also enables you to write your code in a point-free, functional style.
The library is built on two concepts: modules, and interfaces (aka typeclasses). Every value has a module. This is almost always the value's constructor. A module provides all the functions that operate on a type.
An interface defines a set of operations that must be implemented by a module for a type. Typically, implementing an interface allows for other operations to be performed on the value. The idea is that a little effort can result in a large payoff in terms of the functionality that lambdash can provide for your types.
Lamdash attempts to provide a mostly pure functional api. This includes curried functions and operations that do not modify the values they operate on (immutability).
Contents
- Interfaces
- Modules For Built-in Types
- Creating Types
- Roadmap
- Additional Libraries
- Contributing
- Tests
Lambdash defines several standard interfaces (a.k.a. type classes) for custom and built in types. These interfaces allow generic functions to operate on implementing types in a manner specific to the data type being operated on. In general, these interfaces mirror the standard type classes in Haskell, though many functions are renamed to those more familiar to JavaScript.
Eq is an interface which defines equality between items of the same type. In particular, it is intended to test for structural equality.
eq
:a -> a -> Boolean
neq
:a -> a -> Boolean
var arr1 = [1,2,3];
var arr2 = [1,2,3];
var arr3 = [1,2,3,4];
_.eq(arr1, arr2); // true
_.neq(arr1, arr2); // false
_.eq(arr1, arr3); // false
_.neq(arr1, arr3); // true
Ord is an interface for the ordering of elements.
To facilitate ordering, Lambdash provides an algebraic data type for ordering called Ordering.
// represents a "less than result"
_.Ordering.LT === _.LT
// represents a "greater than result"
_.Ordering.GT === _.GT
// represents an "equal to result"
_.Ordering.EQ === _.EQ
Types that implement Ord must also implement Eq.
compare
:a -> a -> Ordering
lt
:a -> a -> Boolean
gt
:a -> a -> Boolean
lte
:a -> a -> Boolean
gte
:a -> a -> Boolean
min
:a -> a -> a
max
:a -> a -> a
_.compare(1, 2); // _.LT
_.compare('cat', 'cab'); // _.GT
_.compare([1,2,3], [1,2,3]); // _.EQ
_.lt(1, 2); // true
_.gt('boat', 'coat'); // false
_.min(43, 56); // 43
_.max([1,2,3], [1,2,4]) // [1,2,4]
Bounded is an interface for types that have a maximum and minimum bound
minBound
:() -> a
maxBound
:() -> a
isMin
:a -> Boolean
isMax
:a -> Boolean
Additionally, the generic _.minBound
and _.maxBound
functions accepts a value and return the minBound and maxBound for the values type, respectively.
_.isMin(false); // true
_.isMin(true); // false
_.isMax(false); // false
_.isMax(true); // true
_.minBound(true); // false
_.maxBound(true); // true
The Enum interface is for types whose values are enumerable. In particular, implementing values may be converted to and from an integer in a consistent fashion.
Values which implement Enum may or may not also implement Bounded.
toInt
:a -> Integer
fromInt
:Integer -> a
prev
:a -> a
next
:a -> a
enumTo
:a -> a -> Array a
enumUntil
:a -> a -> Array a
enumFrom
:Integer -> a -> Array a
_.enumTo(1,5); // [1,2,3,4,5]
_.enumUntil(1,5); // [1,2,3,4]
_.enumTo(5,1); // [5,4,3,2,1]
_.enumUntil(5,1); // [5,4,3,2]
_.enumTo('A', 'E'); // ['A','B','C','D','E']
_.enumTo(true, false); // [true,false]
_.enumFrom(4, 'D'); // ['D','E','F','G']
_.enumFrom(-3, 'Q'); // ['Q','P','O']
_.enumFrom(2)('r'); // ['r','s']
_.enumFrom(_,2)(3); // [2,3,4]
The Numeric interface is for types upon whose values algebraic operations can be performed.
toNum
->a -> Number
fromNum
->Number -> a
The following functions may be provided for optimization. If they are not provided, operations, in most cases, are performed by first converting the value to a number then back to the type. It is highly recommended that these functions are implemented rather than relying on the default behavior.
add
:a -> a -> a
sub
:a -> a -> a
mul
:a -> a -> a
div
:a -> a -> a
mod
:a -> a -> a
abs
:a -> a
sign
:a -> a
negate
:a -> a
reciprocal
:a -> a
pow
:a -> a -> a
subBy
:a -> a -> a
divBy
:a -> a -> a
modBy
:a -> a -> a
powBy
:a -> a -> a
A functor is a mappable, structure preserving type.
A functor should conform to the following law:
_.map(_.identity, a) is equal to a
_.map(f, _.map(g, a)) is equal to _.map(_.compose(f,g), a)
map
:Functor f => (a -> b) -> f a -> f b
_.map(x => x + 1, [1,2,3]); // [2,3,4]
A semigroup is a type that whose values can be concatenated together. The following law should be observed:
_.concat(a, _.concat(b, c)) is equal to _.concat(_.concat(a, b), c)
concat
:a -> a -> a
concatAll
:a... -> a
_.concatAll([1,2],[3,4],[5,6]); // [1,2,3,4,5,6]
_.concat([1,2],[3,4]); // [1,2,3,4]
_.concat([1,2])([3,4]); // [1,2,3,4]
_.concat(_,[1,2])([3,4]); // [3,4,1,2]
A type that implements Monoid must also implement Semigroup
A Monoid is a semigroup with an empty value. It must confrom to the following law:
_.concat(_.empty(a), a) is equal to _.concat(a, _.empty(a)) is equal to a
empty
:() -> a
isEmpty
:a -> Boolean
cycleN
:Number -> m -> m
Additionally, the generic _.empty
function accepts a value and returns the empty value for the type.
_.empty([1,2,3]); // []
_.empty("string"); // ""
_.isEmpty([1,2,3]); // false
_.isEmpty([]); // true
_.cycleN(3, [1,2,3]); // [1,2,3,1,2,3,1,2,3];
_.cycleN(4, ["ab"]); // "abababab"
A type that implements applicative may have values which may be applied to other values of the type.
A value that implements Applicative must first implement Functor.
The following should be true:
A.ap(A.of(f),A.of(a)) is equal to A.map(f, A.of(a))
ap
:Applicative p => p (a -> b) -> p a -> p b
of
:Applicative p => a -> p a
_.ap(_.Arr.of(x => x + 1), [1,2,3]); // [2,3,4]
_.ap([x => x + 1, x => x * 2], [1,2,3]); // [2,3,4,2,4,6]
A monad is a chainable container type.
A type that implements Monad must first implement Applicative
flatten
:Monad m => m (m a) -> m a
chain
:Monad m => (a -> m b) -> m a -> m b
Additionally, functions that operate on monads may be composed or piped using the _.composeM
and _.pipeM
functions.
These functions behave in a similar manner to _.compose
and _.pipe
except that the functions are joined with the _.chain
function.
var Maybe = require('lambdash.maybe'),
Just = Maybe.Just,
None = Maybe.None;
_.flatten(Just(Just(1))); // Just(1)
_.flatten(Just(None)); // None
_.flatten(None); // None
_.flatten([[1,2],[3,4],[5,6]]); // [1,2,3,4,5,6]
_.chain(n => [n, n * 2], [1,2,3]); // [1,2,2,4,3,6]
var add1 = x => [_.add(x,1)];
var mul2 = x => [_.mul(x,2)];
var sub2 = x => [_.sub(x,2)];
var composed = _.composeM(add1, mul2, sub2);
composed([3,4,5]); // [3,5,7]
var piped = _.pipeM(add1, mul2, sub2);
piped([3,4,5]); // [6,8,10]
The Foldable interface is for container types that can be folded into a value.
foldl
:Foldable f => (b -> a -> b) -> b -> f a -> b
foldr
:Foldable f => (b -> a -> b) -> b -> f a -> b
foldMap
:(Foldable f, Monoid m) => (a -> m) -> f a -> m
foldMap2Def
:(Foldable f, Monoid m) => (a -> m) -> m -> f a -> m
join
:(Foldable f, Monoid m) => f m -> m
joinDef
:(Foldable f, Monoid m) => m -> f m -> m
joinWith
:(Foldable f, Monoid m) => m -> f m -> m
joinWithDef
:(Foldable f, Monoid m) => m -> m -> f m -> m
toArray
:Foldable f => f a -> Array a
len
:Foldable f => f a -> Integer
isEmpty
:Foldable f => f a -> Boolean
isNotEmpty
:Foldable f => f a -> Boolean
contains
:(Foldable f, Eq a) => a -> f a -> Boolean
notContains
:(Foldable f, Eq a) => a -> f a -> Boolean
any
:Foldable f => (a -> Boolean) -> f a -> Boolean
all
:Foldable f => (a -> Boolean) -> f a -> Boolean
countWith
:Foldable f => (a -> Boolean) -> f a -> Number
count
:(Foldable f, Eq e) => e -> f e -> Number
fold1
:Foldable f => (b -> a -> b) -> f a -> b
foldl1
:Foldable f => (b -> a -> b) -> f a -> b
foldr1
:Foldable f => (b -> a -> b) -> f a -> b
maximum
:(Foldable f, Ord a) => f a -> a
minimum
:(Foldable f, Ord a) => f a -> a
sum
:(Foldable f, Numeric a) => f a -> a
product
:(Foldable f, Numeric a) => f a -> a
The Sequential interface is for containers whose values are ordered and indexable.
A type that implements Sequential must first implement Foldable and Monoid.
nth
:Sequential s => Number -> s a -> a
of
:Sequential s => a -> s a
len
:Sequential s => s -> Number
append
:Sequential s => a -> s a -> s a
prepend
:Sequential s => a -> s a -> s a
slice
:Sequential s => Number -> Number -> s a -> s a
intersperse
:Sequential s => a -> s a -> s a
reverse
:Sequential s => s a -> s a
filter
:Sequential s => (a -> Boolean) -> s a -> s a
uniqueBy
:Sequential s => (a -> b) -> s a -> s a
findIndex
:Sequential s => (a -> Boolean) -> s a -> Number
findLastIndex
:Sequential s => (a -> Boolean) -> s a -> Number
take
:Sequential s => Number -> s a -> s a
drop
:Sequential s => Number -> s a -> s a
takeLast
:Sequential s => Number -> s a -> s a
dropLast
:Sequential s => Number -> s a -> s a
head
:Sequential s => s a -> a
last
:Sequential s => s a -> a
tail
:Sequential s => s a -> s a
init
:Sequential s => s a -> s a
splitAt
:Sequential s => Number -> s a -> Array (s a)
takeWhile
:Sequential s => (a -> Boolean) -> s a -> s a
dropWhile
:Sequential s => (a -> Boolean) -> s a -> s a
takeLastWhile
:Sequential s => (a -> Boolean) -> s a -> s a
dropLastWhile
:Sequential s => (a -> Boolean) -> s a -> s a
unique
:Sequential s => s a -> s a
find
:Sequential s => (a -> Boolean) -> s a -> a
findLast
:Sequential s => (a -> Boolean) -> s a -> a
indexOf
:Sequential s => a -> s a -> Number
lastIndexOf
:Sequential s => a -> s a -> Number
SetOps is an interface for performing common set operations on
union
:SetOps s => s -> s -> s
difference
:SetOps s => s -> s -> s
intersection
:SetOps s => s -> s -> s
symmetricDifference
:SetOps s => s -> s -> s
There are none.
Set is an interface for a collection of (logical) unique values.
A type that implements set must also implement SetOps.
exists
:Set s => k -> s k -> Boolean
insert
:Set s => k -> s k -> s k
remove
:Set s => k -> s k -> s k
There are none.
The Associative interface is for containers that map keys to values.
assoc
:Associative a => k -> v -> a k v -> a k v
dissoc
:Associative a => k -> a k v -> a k v
exists
:Associative a => k -> a k v -> Boolean
lookup
:Associative a => k -> a k v -> v
These functions may be implemented, but they are not required nor are they derived.
keys
:Associative a => a k v -> Array k
values
:Associative a => a k v -> Array v
mapAssoc
:Associative a => (v -> k -> v) -> a k v -> a k v
foldlAssoc
:Associative a => (b -> v -> k -> b) -> b -> a k v -> b
foldrAssoc
:Associative a => (b -> v -> k -> b) -> b -> a k v -> b
filterAssoc
:Associative a => (v -> k -> Boolean) -> a k v -> a k v
lookupAll
:(Functor f, Associative a) => f k -> a k v -> f v
lookupOr
:Associative a => v -> k -> a k v -> v
update
:Associative a => k -> (v -> v) -> a k v -> a k v
updateOr
:Associative a => v -> k -> (v -> v) -> a k v -> a k v
Additionally, if the type implements foldlAssoc the following is derived:
pairs
:Associative a => a k v -> Array Array(k,a))
The show type is for converting a datatype to a string representation.
show
:Show s => s -> String
Lambdash provides a module for most of the built-in types. If one is not provided, it the Obj module is the default. All of the functions listed for each module are attached to the root lambdash object.
The any type can be any value. It exists for validation when creating algebraic data types.
Nothing.
None.
The Bool module is for Boolean values.
- Eq
- Ord
- Enum
- Show
and
:Boolean -> Boolean -> Boolean
or
:Boolean -> Boolean -> Boolean
xor
:Boolean -> Boolean -> Boolean
not
:Boolean -> Boolean
both
:(*... -> Boolean) -> (*... -> Boolean) -> (*... -> Boolean)
either
:(*... -> Boolean) -> (*... -> Boolean) -> (*... -> Boolean)
neither
:(*... -> Boolean) -> (*... -> Boolean) -> (*... -> Boolean)
eitherExclusive
:(*... -> Boolean) -> (*... -> Boolean) -> (*... -> Boolean)
complement
:(*... -> Boolean) -> (*... -> Boolean)
condition
:[(*... -> Boolean), (*... -> a)]... -> (*... -> a)
T
:() -> Boolean
F
:() -> Boolean
The Num module is for Number values.
- Eq
- Ord
- Enum
- Numeric
- Show
No additional functions.
The Int module is only meant to act as a constraint for numbers that should always be integral. Currently a number will always use the Num module for its functionality.
Nothing
No additional functions.
The Str module is for string values.
- Eq
- Ord
- Enum (uses the first character of the string)
- Functor
- Semigroup
- Monoid
- Foldable
- Sequential
- Show
split
:String|RegExp -> String -> Array String
match
:RegExp -> String -> Array String
replace
:String|RegExp -> String -> String -> String
toLower
:String -> String
toUpper
:String -> String
trim
:String -> String
lines
:String -> Array String
words
:String -> Array String
unlines
:Foldable f => f String -> String
unwords
:Foldable f => f String -> String
Arr is the lambdash module for Arrays.
- Eq (if its elements implement Eq)
- Ord (if its elements implement Ord)
- Functor
- Foldable
- Semigroup
- Monoid
- Applicative
- Monad
- Sequential
- SetOps
- Set
- Show (if its elements implement Show)
applyTo
:(* -> *) -> Array * -> *
Object is the lambdash module for objects.
- Eq (if its values implement Eq)
- Ord (if its values implement Ord)
- Functor
- Semigroup (concat is a right biased union)
- Monoid
- SetOps
- Foldable
- Associative (with optional functions)
- Show (if its values implement Show, keys are sorted)
copy
:{String: a} -> {String: a}
copyOwn
:{String: a} -> {String: a}
propExists
:String -> {String: a} -> Boolean
ownPropExists
:String -> {String: a} -> Boolean
prop
:String -> {String: a} -> a|undefined
propOr
:a -> String -> {String: a} -> a
props
:Foldable f => f String -> {String: a} -> f a
propNames
:{String: a} -> [String]
ownPropNames
:{String: a} -> [String]
ownValues
:{String: a} -> [a]
ownPairs
:{String: a} -> [[String, a]]
filter
:(a -> Boolean) -> {String: a} -> {String: a}
Regex is the lambdash module for regular expression objects.
- Show
test
:String -> RegExp -> Boolean
exec
:String -> RegExp -> [String]
A 3rd-party package could be created to extend this functionality.
DT is the lambdash module for DateTime objects.
- Eq
- Ord
- Numeric
- Show
None.
A 3rd-party package should be created to extend this functionality.
Fun is the lambdash module for Functions.
Nothing.
compose
:(*... -> *)... -> (*... -> *)
pipe
:(*... -> *)... -> (*... -> *)
always
:a -> (() -> a)
alwaysThrow
:(*... -> Error) -> *... -> (() -> ())
thunk
:(*... -> *) -> *... -> (() -> *)
identity
:a -> a
curry
:(*...-> *) -> (*... -> *)
curryN
:Number -> (*... -> *) -> (*... -> *)
arity
:Number -> (*... -> *) -> (*... -> *)
make
:String -> (*... -> *) -> (*... -> *)
thisify
:(*... -> *) -> (*... -> *)
liftN
:Applicative a => -> Number -> (*... -> c) -> ((a *)... -> a c)
lift
:(*... -> c) -> ((a *)... -> a c)
apply
:Foldable f => f * -> (*... -> a) -> a
noop
:() -> ()
flip
: ``
Unit is the module for undefined and null values.
- Eq (always true)
- Ord (always EQ)
- Semigroup (always null)
- Monoid
- Functor (always null)
- Show
None
The library does not yet support ECMAScript 2015 types. There are plans for adding support for them.
Lambdash provides a way to declare new algebraic data types. It also provides a mechanism that will make lambdash start treating your types as modules, which allows all of lambdash's generic functions to operate on your types instances.
Lambdash does not all care about an objects prototype. Instead, it cares about modules. Every object has module. For custom types, by default, this module is lamdash's Obj module. However, the whole point of the library is to allow the user the ability to create their own types which will have an equal status with all the other types.
Lamdash provides a function, _.Type.moduleFor
, which will return the module for a value.
The following is the process through which the module is identified:
- If the value is null or undefined, return the Unit module
- If the values constructor is a module and a submodule, return the constructor's parent module.
- If the values constructor is a module, return the constructor
- Return the appropriate module for built-in types.
A type can be declared as a module using the _.Type.module
function.
// this example uses a constructor function since it is an easy
// way to set a constructor for a value
function Example(x) {
this.whatever = x
}
var ex = new Example(1);
_.Type.moduleFor(ex); // _.Type.Obj
// make Example a module
_.Type.module(Example);
_.Type.ModuleFor(ex); // Example
Submodules can be defined with the _.Type.subModule
function.
The first parameter is the parent module, the second is the submodule.
The submodule should also be declared as a module.
This functionality exists for sum types.
Modules will probably also want to add a member
function.
The member
function should accept a single value and return true if the value
is a member of the type, false if not.
Lamdash has a way to create product types. A product type is a type that has a fixed number of labelled or non-labelled elements.
Product types are automatically created as modules.
A Product type should specify a name as its first argument and a definition as its second argument. The definition may be an object or an array. If the definition is an object, the keys will be the fields of the product type and the values will be constraints. If the definition is an array, its values should be constraints.
Constraints may be a type with a member function, a function that accepts a value and returns true or false, or null. If the constraint is null, the value may be of any type, but must be defined.
Every product type will automatically implement Eq (assuming its field implement Eq). They will also have an unapply function.
var Point = _.Type.product('Point', {x: _.Num, y: _.Num});
var p = Point(1,2);
p instanceof Point; // true
_.Type.moduleFor(p); // Point
Point.name; // "Point"
Point.member(point); // true
p.x; // 1
p.y; // 2
p[0]; // 1
p[1]; // 2
var p2 = Point('a', 'b'); // TypeError
// with an array
var Point = _.Type.product('Point', [_.Num, _.Num]);
p._0; // 1
p._1; // 2
p[0]; // 1
p[1]; // 2
_.eq(Point(1,2), Point(1,2)); // true
_.eq(Point(1,2), Point(1,3)); // false
_.unapply(function(x,y){console.log('x:'+x+',y:',y)}, p); // 'x:1,y:2'
Every product type will automatically implement Eq (assuming its field implement Eq). They will also have an unapply function.
Sum types are a type that can be an instance of any one of its sub types.
To create a sum type pass a name as a string or a named function as the first
parameter, and a definition object to the second parameter of the _.Type.sum
function.
The definition must be an object with the keys as the names of the subtypes, and
the values as product type definitions.
The keys must be valid identifier names.
If a definition of a subtype is empty, a single instance will be created and attached to the sum type rather than the constructor.
Every sum type will implement Eq and will also have a case function which can be use for simple matching of the type of the value.
var Maybe = _.Type.sum('Maybe', {'None': [], 'Some': {value: _.Any}});
var maybe = Maybe.None;
maybe instancof Maybe; // true
Maybe.member(maybe); // true
_.Type.moduleFor(maybe); // Maybe
maybe = Maybe.Some(1);
maybe instanceof Maybe; // true
maybe instanceof Maybe.Some; // true
Maybe.member(maybe); // true
var value = _.case({
None: 0,
Some: function(v) {
return v + 1;
}
}, maybe);
// value is 2
// case with a default
value = _.case({
None: 1,
_: 0
}, maybe);
// value is 0
// creating maybe with a function
var Maybe = function(value) {return value == null ? Maybe.None : Maybe.Some(value) }
Maybe = _.Type.sum(Maybe, {'None': [], 'Some': {value: _.Any}});
Maybe(1); // Some(1)
Maybe(null); // None
Enumerated types are a special case of Sum types in which there are no values associated with any of the sub types.
Enumerated types will automatically implement Ord, Bounded, and Enum.
var Days = _.Type.enumerated('Days', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
_.enumFrom(Days.Tue, Days.Fri); // [Tue, Wed, Thu, Fri]
_.lt(Days.Tue, Days.Thu); // true
Days.maxBound(); // 'Sun'
Days.minBound(); // 'Mon'
Custom types may implement interfaces by creating the correct functions.
For example, consider implementing functor for the maybe type.
The functor interface requires that the type implement Map
.
var Maybe = _.Type.sum('Maybe', {None: [], Some: {value: _.Any}});
var m = Maybe.Some(1);
_.Functor.member(m); // false
Maybe.map = _.curry(function(fn, maybe){
if (maybe === Maybe.None) {
return maybe;
}
return Maybe.Some(fn(maybe.value));
});
_.Functor.member(m); // true
_.map(_.add(2), m); // Some(3)
_.map(_.add(2), Maybe.None); // None
I feel that the 0.6.0 release is very significant release. It is the first release that I actually feel comfortable using in a real application.
That being said, there is more work to do.
After the 0.6.0 release, I intend on improving the workflow.
The master branch will always contain the most recent stable release and development
will take place in other branches.
The following is either planned or under review for the 0.7.0 release.
- Implement modules for the new ECMAScript 2015 types
- Figure out what to do with the Int module and what exactly its role in the library is
- Adding Unicode processing functions to the Str module
- Possibly adding a way to create a Union type (like a sum type from existing types)
- Improve and further standardize docblocks and generate api docs
- Create a script that will amalgamate the files into one and minimize for the browser.
- Create benchmarks.
- Create a website for the library.
- Create an external module that will improve or replace the DT module.
Additional libraries for lambdash are either available or under development.
- Task (For asynchronous computations)
- List
- Maybe
- Either
If you have developed an additional library for lamdash, please let me know so that I can list it here.
I am happy to take pull requests If anybody wants to contribute.
To run tests:
npm install --dev
npm test
To run the coverage report:
npm run coverage