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
Transform | ADT signature | Function Signature | Location |
---|---|---|---|
arrayToList | [ a ] -> List a | (a -> [ b ]) -> a -> List b | crocks/List |
asyncToPromise | Async e a -> Promise a e | (a -> Async e b) -> a -> Promise b e | crocks/Async |
eitherToAsync | Either e a -> Async e a | (a -> Either e b) -> a -> Async e b | crocks/Async |
eitherToFirst | Either b a -> First a | (a -> Either c b) -> a -> First b | crocks/First |
eitherToLast | Either b a -> Last a | (a -> Either c b) -> a -> Last b | crocks/Last |
eitherToMaybe | Either b a -> Maybe a | (a -> Either c b) -> a -> Maybe b | crocks/Maybe |
eitherToResult | Either e a -> Result e a | (a -> Either e b) -> a -> Result e b | crocks/Result |
firstToAsync | e -> First a -> Async e a | e -> (a -> First b) -> a -> Async e b | crocks/Async |
firstToEither | c -> First a -> Either c a | c -> (a -> First b) -> a -> Either c b | crocks/Either |
firstToLast | First a -> Last a | (a -> First b) -> a -> Last b | crocks/Last |
firstToMaybe | First a -> Maybe a | (a -> First b) -> a -> Maybe b | crocks/Maybe |
firstToResult | c -> First a -> Result c a | c -> (a -> First b) -> a -> Result c b | crocks/Result |
lastToAsync | e -> Last a -> Async e a | e -> (a -> Last b) -> a -> Async e b | crocks/Async |
lastToEither | c -> Last a -> Either c a | c -> (a -> Last b) -> a -> Either c b | crocks/Either |
lastToFirst | Last a -> First a | (a -> Last b) -> a -> First b | crocks/First |
lastToMaybe | Last a -> Maybe a | (a -> Last b) -> a -> Maybe b | crocks/Maybe |
lastToResult | c -> Last a -> Result c a | c -> (a -> Last b) -> a -> Result c b | crocks/Result |
listToArray | List a -> [ a ] | (a -> List b) -> a -> [ b ] | crocks/List |
maybeToArray | Maybe a -> [ a ] | (a -> Maybe b) -> a -> [ b ] | crocks/Maybe |
maybeToAsync | e -> Maybe a -> Async e a | e -> (a -> Maybe b) -> a -> Async e b | crocks/Async |
maybeToEither | c -> Maybe a -> Either c a | c -> (a -> Maybe b) -> a -> Either c b | crocks/Either |
maybeToFirst | Maybe a -> First a | (a -> Maybe b) -> a -> First b | crocks/First |
maybeToLast | Maybe a -> Last a | (a -> Maybe b) -> a -> Last b | crocks/Last |
maybeToList | Maybe a -> List a | (a -> Maybe b) -> a -> List b | crocks/List |
maybeToResult | c -> Maybe a -> Result c a | c -> (a -> Maybe b) -> a -> Result c b | crocks/Result |
resultToAsync | Result e a -> Async e a | (a -> Result e b) -> a -> Async e b | crocks/Async |
resultToEither | Result e a -> Either e a | (a -> Result e b) -> a -> Either e b | crocks/Either |
resultToFirst | Result e a -> First a | (a -> Result e b) -> a -> First b | crocks/First |
resultToLast | Result e a -> Last a | (a -> Result e b) -> a -> Last b | crocks/Last |
resultToMaybe | Result e a -> Maybe a | (a -> Result e b) -> a -> Maybe b | crocks/Maybe |
tupleToArray | Tuple a -> [ a ] | (a -> Tuple b) -> a -> [ b ] | crocks/Tuple |
writerToPair | Writer m a -> Pair m a | (a -> Writer m b) -> a -> Pair m b | crocks/Pair |
Contribute on Github! Edit this section.