Experimental The api can change between versions.
exalted.future
A javascript and typescript monadic library & functional fun. fantasy-land compliant, mostly.
The style of monad object is inspired by DrBoolean course on egghead.io and his book Mostly adequate guide to FP.
The choice for cata
, encase
, head
, tail
, last
is inspired by rametta's take on monads in pratica.
This is, in many ways, an evolution of oncha which I wrote with other people many years ago and is no longer maintained.
Natural Transformation
One of the main goals of the exalted.future is to make it possible to rely on natural transformation when composing Monads. So that you can write in one continuous flow your program, agnostic of the Monad you are working with. That is why mapReduce
, reduce
, fold
, and fork
use the same language, cata
. You can always call cata on an object, and it will compute your results. The following example attempts to illustrate that. Regardless that the fetch succeeds or fails the outcome will be the same, indifferent to calling cata
on Maybe, Either (Left|Right), or Future.
Future.promise(fetch('https://jsonplaceholder.typicode.com/todos/1'))
.chain(response => Either.encase(() => response.json()))
.chain(Future.promise)
.chain(Maybe)
.map(todo => todo.title)
.map(t => t.toUpperCase())
.cata({
Left: e => console.log('oops', e),
Right: json => console.log(json)
})
Install
yarn add exalted.future
Types
- There is a divergence from fantasy-land where
reduce
is named cata
and loosely based on daggy's union types. fold
always folds on identity a => a
, except when it does not like with the Future
.Maybe.map
will return Nothing if the callback function returns null. In other words Just(null)
is impossible, unless you call the 'static' constructor like this Just.of(null)
. See this pr for some explanation.- Left is not 100% applicative.
- Not all functions are documented, so you are encouraged to read the source code, you'll find
bimap
, swap
, fold
, foldr
, foldf
.
Future
A Future monad for async computation. Left
is reject and Right
is resolve. Because Right
is always right and Left is not.
Future((err, ok) => ok('Yay'))
.map(res => res.toUpperString())
.cata({
Left: err => log(`Err: ${err}`),
Right: res => log(`Res: ${res}`)
})
Future.promise(fetch('https://api.awesome.com/catOfTheDay'))
.cata({
Left: err => log('There was an error fetching the cat of the day :('),
Right: cat => log('Cat of the day: ' + cat)
})
Future.promise(fetch('https://api.awesome.com/catOfTheDay'))
.chain(cat => Future.promise(fetch(`https://api.catfacts.com/${cat}`)))
.cata({
Left: err => log('There was an error fetching the cat of the day :('),
Right: facts => log('Facts for cat of the day: ' + facts)
})
Future.all
Concats all the results form the list of futures.
all :: ([Futures]) -> b
Future.all(
Future.of('apple'),
Future((left, right) => setTimeout(() => right('orange'), 1000)),
Future.of('lemon')
).cata({
Left: () => (),
Right: ([ apple, orange, lemon ]) => console.log(apple, orange, lemon)
})
Id
Identity monad.
Id(5)
.map(num => num * 7)
.map(num => num - 1)
.cata({
Right: a => a
})
Maybe
Maybe monad.
Maybe('Hello exalted one')
.map(sentence => sentence.toUpperString())
.map(sentence => `${sentence}!`)
.cata({
Right: console.log
})
Maybe(null)
.map(sentence => sentence.toUpperString())
.alt(() => 'Maybe received a null')
.cata({
Right: console.log
})
Either
An Either monad and nullable, Left, Right.
nullable('Hello')
.cata({
Left: () => 'Oops',
Right: val => `${val} world!`
})
nullable(null)
.cata({
Left: () => 'Oops',
Right: val => `${val} world!`
})
const extractEmail = obj => obj.email ? Right(obj.email) : Left()
extractEmail({ email: 'test@example.com' }
.map(extractDomain)
.cata({
Left: () => 'No email found!',
Right:x => x
})
extractEmail({ name: 'user' }
.map(extractDomain)
.cata({
Left: () => 'No email found!',
Right: x => x
})
Functions
The following functions are common to all monads types.
alt
Sets the value to cata on.
alt :: Any -> Nothing of Any
Maybe(1).alt(5).cata({
Right: a => a
})
Maybe(null).alt(5).cata({
Right: a => a
})
ap
Apply
chain :: (a -> b) -> b
Id(5).chain(a => Id(a))
Id(Id(5)).chain(a => a)
cata
Foldable
cata :: ({ Left: () -> b, Right -> a -> a }) -> a | b
Id(5).cata({
Right: a => a
})
Id(5).cata({
Right: a => a + 1
})
Right(5).cata({
Left: a => 8
Right: a => a + 1
})
Left(5).cata({
Left: a => a + 1
Right: a => 8
})
Maybe(5).cata({
Right: a => a
})
Maybe(5).cata({
Left: () => { }
Right: a => a + 1
})
Maybe(null).cata({
Left: () => 'there was a null'
Right: a => a + 1
})
Right(5).cata({
Left: () => 1,
Right: a => a + 2
})
Left(5).cata(a => a + 1)
chain
Chain
chain :: (a -> b) -> b
Id(5).chain(a => Id(a))
Id(Id(5)).chain(a => a)
compose
Compose takes n functions as arguments and return a function.
const transform = compose(sentence => sentence.toUpperString(), sentence => `${sentence}!`)
const logTransform = compose(log, transform)
logTransform('Hello exalted one')
compose(path.normalize, path.join)('./exalted', '/one')
equals
Setoid
equals :: Id -> Boolean
Id(1).equals(Id(1))
Id(2).equals(Id(1))
Id(2).equals(Id(1)) === Id(1).equals(Id(1))
inspect
inspect :: () -> String
Id(5).inspect()
map
Functor
map :: (a -> b) -> Id of b
Id(7).map(a => a * 2)
map (first class)
Map as partial application and first class with arity support.
map(a => a + 1, a => a * 3)([1, 2, 3])
of
Applicative
of :: a -> Id of a
Id(5).of(6)
Id(5).of(Id(6))
head, tail, last
Returns a Maybe.
head([1,2])
head([])
tail([1,2,3])
tail([])
last([1,2,3])
last([])
Maybe.encase, Either.encase
Returns Left | Right
.
Maybe.encase(() => JSON.parse('["foo","bar","baz"]'))
Maybe.encase(() => JSON.parse('['))
Either.encase(() => JSON.parse('["foo","bar","baz"]'))
Either.encase(() => JSON.parse('['))