Transformation Functions

Transformation functions are mostly used to reduce unwanted nesting of similar types. Take for example the following structure:

import Either from 'crocks/Either'
import Maybe from 'crocks/Maybe'
import identity from 'crocks/combinators/identity'
import map from 'crocks/pointfree/map'
import option from 'crocks/pointfree/option'
const data =
Either.of(Maybe.of(3))
//=> Right Just 3
// mapping on the inner Maybe is tedious at best
data
.map(map(x => x + 1)) //=> Right Just 4
.map(map(x => x * 10)) //=> Right Just 40
// and extraction...super gross
data
.either(identity, identity) //=> Just 3
.option(0) //=> 3
// or
data
.either(option(0), option(0)) // 3

The transformation functions, that ship with crocks, provide a means for dealing with this. Using them effectively, can turn the above code into something more like this:

import Either from 'crocks/Either'
import Maybe from 'crocks/Maybe'
import identity from 'crocks/combinators/identity'
import map from 'crocks/pointfree/map'
import maybeToEither from 'crocks/Either/maybeToEither'
const data =
Either.of(Maybe.of(3)) //=> Right Just 3
.chain(maybeToEither(0)) //=> Right 3
// mapping on a single Either, much better
data
.map(x => x + 1) //=> Right 4
.map(x => x * 10) //=> Right 40
// no need to default the Left case anymore
data
.either(identity, identity)
//=> 3
// effects of the inner type are applied immediately
const nested =
Either.of(Maybe.Nothing)
//=> Right Nothing
const unnested =
nested
.chain(maybeToEither(0))
//=> Left 0
// Always maps, although the inner Maybe skips
nested
.map(map(x => x + 1)) //=> Right Nothing (runs mapping)
.map(map(x => x * 10)) //=> Right Nothing (runs mapping)
.either(identity, identity) //=> Nothing
.option(0) //=> 0
// Never maps on a Left, just skips it
unnested
.map(x => x + 1) //=> Left 0 (skips mapping)
.map(x => x * 10) //=> Left 0 (skips mapping)
.either(identity, identity) //=> 0

Not all types can be transformed to and from each other. Some of them are lazy and/or asynchronous, or are just too far removed. Also, some transformations will result in a loss of information. Moving from an Either to a Maybe, for instance, would lose the Left value of Either as a Maybe's first parameter (Nothing) is fixed at Unit. Conversely, if you move the other way around, from a Maybe to an Either you must provide a default Left value. Which means, if the inner Maybe results in a Nothing, it will map to Left of your provided value. As such, not all of these functions are guaranteed isomorphic. With some types you just cannot go back and forth and expect to retain information.

Each function provides two signatures, one for if a Function is used for the second argument and another if the source ADT is passed instead. Although it may seem strange, this provides some flexibility on how to apply the transformation. The ADT version is great for squishing an already nested type or to perform the transformation in a composition. While the Function version can be used to extend an existing function without having to explicitly compose it. Both versions can be seen here:

import Either from 'crocks/Either'
import Maybe from 'crocks/Maybe'
import Async from 'crocks/Async'
import safeLift from 'crocks/Maybe/safeLift'
import maybeToAsync from 'crocks/Either/maybeToAsync'
import eitherToMaybe from 'crocks/Maybe/eitherToMaybe'
import compose from 'crocks/helpers/compose'
import isNumber from 'crocks/predicates/isNumber'
// Avoid nesting
// inc :: a -> Maybe Number
const inc =
safeLift(isNumber, x => x + 1)
// using Function signature
// asyncInc :: a -> Async Number Number
const asyncInc =
maybeToAsync(0, inc)
// using ADT signature to compose (extending functions)
// asyncInc :: a -> Async Number Number
const anotherInc =
compose(maybeToAsync(0), inc)
// resolveValue :: a -> Async _ a
const resolveValue =
Async.Resolved
resolveValue(3) // Resolved 3
.chain(asyncInc) // Resolved 4
.chain(anotherInc) // Resolved 5
.chain(compose(maybeToAsync(20), inc)) // Resolved 6
resolveValue('oops') // Resolved 'oops'
.chain(asyncInc) // Rejected 0
.chain(anotherInc) // Rejected 0
.chain(compose(maybeToAsync(20), inc)) // Rejected 0
// Squash existing nesting
// Just Right 'nice'
const good =
Maybe.of(Either.Right('nice'))
// Just Left 'not so nice'
const bad =
Maybe.of(Either.Left('not so nice'))
good
.chain(eitherToMaybe) // Just 'nice'
bad
.chain(eitherToMaybe) // Nothing

Transformation Signatures#

TransformADT signatureFunction SignatureLocation
arrayToList[ a ] -> List a(a -> [ b ]) -> a -> List bcrocks/List
asyncToPromiseAsync e a -> Promise a e(a -> Async e b) -> a -> Promise b ecrocks/Async
eitherToAsyncEither e a -> Async e a(a -> Either e b) -> a -> Async e bcrocks/Async
eitherToFirstEither b a -> First a(a -> Either c b) -> a -> First bcrocks/First
eitherToLastEither b a -> Last a(a -> Either c b) -> a -> Last bcrocks/Last
eitherToMaybeEither b a -> Maybe a(a -> Either c b) -> a -> Maybe bcrocks/Maybe
eitherToResultEither e a -> Result e a(a -> Either e b) -> a -> Result e bcrocks/Result
firstToAsynce -> First a -> Async e ae -> (a -> First b) -> a -> Async e bcrocks/Async
firstToEitherc -> First a -> Either c ac -> (a -> First b) -> a -> Either c bcrocks/Either
firstToLastFirst a -> Last a(a -> First b) -> a -> Last bcrocks/Last
firstToMaybeFirst a -> Maybe a(a -> First b) -> a -> Maybe bcrocks/Maybe
firstToResultc -> First a -> Result c ac -> (a -> First b) -> a -> Result c bcrocks/Result
lastToAsynce -> Last a -> Async e ae -> (a -> Last b) -> a -> Async e bcrocks/Async
lastToEitherc -> Last a -> Either c ac -> (a -> Last b) -> a -> Either c bcrocks/Either
lastToFirstLast a -> First a(a -> Last b) -> a -> First bcrocks/First
lastToMaybeLast a -> Maybe a(a -> Last b) -> a -> Maybe bcrocks/Maybe
lastToResultc -> Last a -> Result c ac -> (a -> Last b) -> a -> Result c bcrocks/Result
listToArrayList a -> [ a ](a -> List b) -> a -> [ b ]crocks/List
maybeToArrayMaybe a -> [ a ](a -> Maybe b) -> a -> [ b ]crocks/Maybe
maybeToAsynce -> Maybe a -> Async e ae -> (a -> Maybe b) -> a -> Async e bcrocks/Async
maybeToEitherc -> Maybe a -> Either c ac -> (a -> Maybe b) -> a -> Either c bcrocks/Either
maybeToFirstMaybe a -> First a(a -> Maybe b) -> a -> First bcrocks/First
maybeToLastMaybe a -> Last a(a -> Maybe b) -> a -> Last bcrocks/Last
maybeToListMaybe a -> List a(a -> Maybe b) -> a -> List bcrocks/List
maybeToResultc -> Maybe a -> Result c ac -> (a -> Maybe b) -> a -> Result c bcrocks/Result
resultToAsyncResult e a -> Async e a(a -> Result e b) -> a -> Async e bcrocks/Async
resultToEitherResult e a -> Either e a(a -> Result e b) -> a -> Either e bcrocks/Either
resultToFirstResult e a -> First a(a -> Result e b) -> a -> First bcrocks/First
resultToLastResult e a -> Last a(a -> Result e b) -> a -> Last bcrocks/Last
resultToMaybeResult e a -> Maybe a(a -> Result e b) -> a -> Maybe bcrocks/Maybe
tupleToArrayTuple a -> [ a ](a -> Tuple b) -> a -> [ b ]crocks/Tuple
writerToPairWriter m a -> Pair m a(a -> Writer m b) -> a -> Pair m bcrocks/Pair