A few Missing Monads for Javascript and Typescript.
Option(aka Maybe): represents optional values and avoids ugly tests fornull,undefinedorNaNSeq: 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
Iterableincluding 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 ): RangeRanges 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