A few Missing Monads for Javascript and Typescript.
Option
(aka Maybe): represents optional values and avoids ugly tests fornull
,undefined
orNaN
Seq
: an ordered sequence of values with numerous methods to manipulate itRange
: numeric values in range [start;end) with non-zero step value stepTry
: a computation that may either result in an error, or return a successfully computed value and that may be easily chained with other computations
A small library with no external dependencies that implements a few useful Monads for functional programming.
- no external dependencies
- works with ES5
- based on iterators with lazy evaluation
- largely inspired from Scala
- typings automatically loaded when programming in Typescript
- excellent tests coverage
Write shorter immutable code
Instead of
let value;
if (typeof input === 'undefined' || input === null) {
value = getDefaultValue()
}
else {
value = calculateFromInput(input)
}
write
const value = option(input).map(calculateFromInput).getOrElse(getDefaultValue)
Write lazy evaluated code
const mySeq = seq(hugeArray).drop(2).map(expensiveOp).takeFirst(3)
mySeq.toArray
mySeq
is not evaluated (iterated) until toArray
is called on it, and when it is, only five iterations on the Seq and 3 calls to map
are performed
Typescript / ES6: import { some, seq, .. } from 'm.m'
ES5 var MM = require('m.m')
MIT
Most Wanted: Either
,...
You are most welcome to contribute by opening new Pull Requests.
For new Monads, please get inspiration from the Scala definitions and extend Collection
for the implementation
Represents optional values.
/**
* Creates a None, i.e. an empty Option
*/
function none(): Option<A>
/**
* Create a Some i.e. an Option holding a value
*/
function some<A>( value?: A ): Option<A>
/**
* Create a None if value is undefined or null or NaN
* otherwise create a Some holding that value
*/
function option<A>(value?: A): Option<A>
The most idiomatic way to use an Option is to treat it as a collection or monad and use map
, flatMap
, filter
, or foreach
:
const name = option( request.getParameter('name') )
const upper = name.map( n => n.trim() ).filter( n => n.length !== 0 ).map( n => n.toUpperCase() )
console.log(upper.getOrElse( () => "" ) )
A Seq is an ordered sequence of values
/**
* Create a Seq
*/
function seq<A>( ...values: any[] ): Seq<A>
A Seq
can be created from
- a list of disctrete values e.g.
seq( 1, 2, 3 )
- any
Iterable
including an Array, a String, anotherSeq
, an ES6Map
, etc... e.g.seq( [1 ,2, 3] )
,seq('abcd')
Examples:
seq('dlroW olleH').reverse.mkString(' ') // 'H e l l o W o r l d'
seq(1, 2, 'a', 3, 'b', 4).collect( n => !isNaN(n) )( n => n*2 ).mkString('[', ',', ']') // '[2,4,6,8]'
seq( seq( [1, 2] ), 3, seq( 4, 5 ) ).flatten().toArray // [ 1, 2, 3, 4, 5 ]
The Range class represents numeric values in range with non-zero step value step. A Range is a special case of Seq.
/**
* Create a range of integers of the specified length starting at 0
*/
function range( length: number ): Range;
/**
* Create a range from the specified start to the element (end - 1) included in steps of 1
*/
function range( start: number, end: number ): Range
/**
* Create a range from start to (end - step) included
*/
function range( start: number, end: number, step: number ): Range
Ranges are not indexed but based on (lazy) iterators; it is therefore possible to manipulate Infinity
in Ranges:
range(0, Infinity).take(10).toArray //[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
A Try represents a computation that may either result in an error, or return a successfully computed value.
A computation can be created by passing function without arguments to tri()
.
/**
* Wraps this computation in a Try
*/
function tri<A>( computation: ( ) => A ): Try<A>
Please note that the function is spelled with an 'i' to avoid a conflict with the reserved keyword try
As with other monads of this library, Try
is lazy and the computation will only be performed when attempting to "extract" a result.
What is interesting with Try
is the ability to chain error throwing functions with a fail fast behavior, and
potentially recover from these error(s)
function divide( numerator: any, denominator: any ): Try<number> {
const parseNumerator = () => option( parseFloat( numerator ) ).orThrow( () => "Invalid numerator" );
const parseDenominator = () => option( parseFloat( denominator ) ).orThrow( () => "Invalid denominator" );
//chain error throwing functions
return tri( parseNumerator ).flatMap( num => tri( parseDenominator ).map( den => num / den ) )
}
//now, divide(), can itself be chained
divide(num, den).map(x => x*2)
//or can be "recovered" from
divide( num, den )
.recover( ( e: Error ) => {
console.log('Divide failed. '+e.message+'. Returning Infinity')
return Infinity
} )
.get
//divide('blah',3) -> Infinity