Skip to content

Commit

Permalink
add maybeToList and maybeToArray transformations
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsoft committed Jun 23, 2019
1 parent aa0092c commit 17a85ff
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 6 deletions.
76 changes: 76 additions & 0 deletions docs/src/pages/docs/crocks/Maybe.md
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,82 @@ Just(Last.empty())
//=> Nothing
```


#### maybeToArray

`crocks/Maybe/maybeToArray`

```haskell
maybeToArray :: Maybe a -> [ a ]
maybeToArray :: (a -> Maybe b) -> a -> [ b ]
```

Used to transform a given `Maybe` instance to an `Array` or
flatten an `Array` of `Maybe` into an `Array` when chained, `maybeToArray` will
turn a [`Just`](#just) instance into a single element `Array`, wrapping the
original value contained within
the [`Just`](#just) instance. All [`Nothing`](#nothing) instances will map to an
empty `Array`.

Like all `crocks` transformation functions, `maybeToArray` has two possible
signatures and will behave differently when passed either
a `Maybe` instance or a function that returns an instance of `Maybe`. When
passed the instance, a transformed `Array` is returned. When passed
a `Maybe` returning function, a function will be returned that takes a given
value and returns an `Array`.

```javascript
import Maybe from 'crocks/Maybe'

import chain from 'crocks/pointfree/chain'
import composeK from 'crocks/helpers/composeK'
import getProp from 'crocks/Maybe/getProp'
import isString from 'crocks/predicates/isString'
import safe from 'crocks/Maybe/safe'

import maybeToArray from 'crocks/Maybe/maybeToArray'

const { Nothing, Just } = Maybe

maybeToArray(Nothing())
//=> []

maybeToArray(Just(33))
//=> [ 33 ]

// flatten :: [ Maybe a ] -> [ a ]
const flatten =
chain(maybeToArray)

flatten([ Just(33), Just('text') ])
//=> [ 33, 'text' ]

flatten([ Just('left'), Nothing(), Just('right') ])
//=> [ 'left', 'right' ]

// getUser :: a -> Maybe String
const getUser = composeK(
safe(isString),
getProp('user')
)

// getUsers :: [ * ] -> [ String ]
const getUsers =
chain(maybeToArray(getUser))

// data :: [ * ]
const data = [
{ user: 'Allison' },
'Ben',
{ user: 'Beth' },
null,
{ user: 'Claire' }
]

getUsers(data)
//=> [ 'Allison', 'Beth', 'Claire' ]
```

#### resultToMaybe

`crocks/Maybe/resultToMaybe`
Expand Down
16 changes: 12 additions & 4 deletions docs/src/pages/docs/functions/transformation-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,23 +171,25 @@ bad
| [`eitherToFirst`][either-first] | `Either b a -> First a` | `(a -> Either c b) -> a -> First b` | `crocks/First` |
| [`eitherToLast`][either-last] | `Either b a -> Last a` | `(a -> Either c b) -> a -> Last b` | `crocks/Last` |
| [`eitherToMaybe`][either-maybe] | `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` |
| [`eitherToResult`][either-result] | `Either e a -> Result e a` | `(a -> Either e b) -> a -> Result e b` | `crocks/Result` |
| [`firstToAsync`][first-async] | `e -> First a -> Async e a` | `e -> (a -> First b) -> a -> Async e b` | `crocks/Async` |
| [`firstToEither`][first-either] | `c -> First a -> Either c a` | `c -> (a -> First b) -> a -> Either c b` | `crocks/Either` |
| [`firstToLast`][first-last] | `First a -> Last a` | `(a -> First b) -> a -> Last b` | `crocks/Last` |
| [`firstToMaybe`][first-maybe] | `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` |
| [`firstToResult`][first-result] | `c -> First a -> Result c a` | `c -> (a -> First b) -> a -> Result c b` | `crocks/Result` |
| [`lastToAsync`][last-async] | `e -> Last a -> Async e a` | `e -> (a -> Last b) -> a -> Async e b` | `crocks/Async` |
| [`lastToEither`][last-either] | `c -> Last a -> Either c a` | `c -> (a -> Last b) -> a -> Either c b` | `crocks/Either` |
| [`lastToFirst`][last-first] | `Last a -> First a` | `(a -> Last b) -> a -> First b` | `crocks/First` |
| [`lastToMaybe`][last-maybe] | `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` |
| [`lastToResult`][last-result] | `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-array] | `Maybe a -> [ a ]` | `(a -> Maybe b) -> a -> [ b ]` | `crocks/Maybe` |
| [`maybeToAsync`][maybe-async] | `e -> Maybe a -> Async e a` | `e -> (a -> Maybe b) -> a -> Async e b` | `crocks/Async` |
| [`maybeToEither`][maybe-either] | `c -> Maybe a -> Either c a` | `c -> (a -> Maybe b) -> a -> Either c b` | `crocks/Either` |
| [`maybeToFirst`][maybe-first] | `Maybe a -> First a` | `(a -> Maybe b) -> a -> First b` | `crocks/First` |
| [`maybeToLast`][maybe-last] | `Maybe a -> Last a` | `(a -> Maybe b) -> a -> Last b` | `crocks/Last` |
| `maybeToResult` | `c -> Maybe a -> Result c a` | `c -> (a -> Maybe b) -> a -> Result c b` | `crocks/Result` |
| `maybeToList` | `Maybe a -> List a` | `(a -> Maybe b) -> a -> List b` | `crocks/List` |
| [`maybeToResult`][maybe-result] | `c -> Maybe a -> Result c a` | `c -> (a -> Maybe b) -> a -> Result c b` | `crocks/Result` |
| [`resultToAsync`][result-async] | `Result e a -> Async e a` | `(a -> Result e b) -> a -> Async e b` | `crocks/Async` |
| [`resultToEither`][result-either] | `Result e a -> Either e a` | `(a -> Result e b) -> a -> Either e b` | `crocks/Either` |
| [`resultToFirst`][result-first] | `Result e a -> First a` | `(a -> Result e b) -> a -> First b` | `crocks/First` |
Expand All @@ -212,6 +214,7 @@ bad
[either-maybe]: ../crocks/Maybe.html#eithertomaybe
[first-maybe]: ../crocks/Maybe.html#firsttomaybe
[last-maybe]: ../crocks/Maybe.html#lasttomaybe
[maybe-array]: ../crocks/Maybe.html#maybetoarray
[result-maybe]: ../crocks/Maybe.html#resulttomaybe

[either-first]: ../monoids/First.html#eithertofirst
Expand All @@ -224,6 +227,11 @@ bad
[maybe-last]: ../monoids/Last.html#maybetolast
[result-last]: ../monoids/Last.html#resulttolast

[either-result]: ../crocks/Result.html#eithertoresult
[first-result]: ../crocks/Result.html#firsttoresult
[last-result]: ../crocks/Result.html#lasttoresult
[maybe-result]: ../crocks/Result.html#maybetoresult

[tuple-array]: ../crocks/Tuple.html#tupletoarray

[maybe]: ../crocks/Maybe.html
Expand Down
43 changes: 43 additions & 0 deletions src/List/maybeToList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/** @license ISC License (c) copyright 2019 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */

const List = require('.')
const Maybe = require('../core/types').proxy('Maybe')

const curry = require('../core/curry')
const isFunction = require('../core/isFunction')
const isSameType = require('../core/isSameType')

const applyTransform = maybe =>
maybe.either(
List.empty,
List.of
)

const err =
'maybeToList: Argument must be a Maybe instanstace or a Maybe returning function'

// maybeToList : Maybe a -> List a
// maybeToList : (a -> Maybe b) -> a -> List b
function maybeToList(maybe) {
if(isFunction(maybe)) {
return function(x) {
const m = maybe(x)

if(!isSameType(Maybe, m)) {
throw new TypeError(err)
}

return applyTransform(m)
}
}

if(isSameType(Maybe, maybe)) {
return applyTransform(maybe)
}

throw new TypeError(err)
}

module.exports = curry(maybeToList)

83 changes: 83 additions & 0 deletions src/List/maybeToList.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const test = require('tape')
const helpers = require('../test/helpers')

const bindFunc = helpers.bindFunc

const List = require('.')
const Maybe = require('../core/Maybe')

const isFunction = require('../core/isFunction')
const isSameType = require('../core/isSameType')

const identity = x => x

const maybeToList = require('./maybeToList')

test('maybeToList transform', t => {
const f = bindFunc(maybeToList)

t.ok(isFunction(maybeToList), 'is a function')

const err = /maybeToList: Argument must be a Maybe instanstace or a Maybe returning function/
t.throws(f(undefined), err, 'throws if first arg is undefined')
t.throws(f(null), err, 'throws if first arg is null')
t.throws(f(0), err, 'throws if first arg is a falsey number')
t.throws(f(1), err, 'throws if first arg is a truthy number')
t.throws(f(''), err, 'throws if first arg is a falsey string')
t.throws(f('string'), err, 'throws if first arg is a truthy string')
t.throws(f(false), err, 'throws if first arg is false')
t.throws(f(true), err, 'throws if first arg is true')
t.throws(f([]), err, 'throws if first arg is an array')
t.throws(f({}), err, 'throws if first arg is an object')

t.end()
})

test('maybeToList with Maybe', t => {
const some = 'something'

const good = maybeToList(Maybe.Just(some))
const bad = maybeToList(Maybe.Nothing())

t.ok(isSameType(List, good), 'returns a List with a Just')
t.ok(isSameType(List, bad), 'returns a List with a Nothing')

t.ok(good.equals(List.of(some)), 'Just maps to single item List containing the value')
t.ok(bad.equals(List.empty()), 'Nothing maps to an empty List')

t.end()
})

test('maybeToList with Maybe returning function', t => {
const some = 'something'

t.ok(isFunction(maybeToList(Maybe.of)), 'returns a function')

const f = bindFunc(maybeToList(identity))

const err = /maybeToList: Argument must be a Maybe instanstace or a Maybe returning function/
t.throws(f(undefined), err, 'throws if function returns undefined')
t.throws(f(null), err, 'throws if function returns null')
t.throws(f(0), err, 'throws if function returns a falsey number')
t.throws(f(1), err, 'throws if function returns a truthy number')
t.throws(f(''), err, 'throws if function returns a falsey string')
t.throws(f('string'), err, 'throws if function returns a truthy string')
t.throws(f(false), err, 'throws if function returns false')
t.throws(f(true), err, 'throws if function returns true')
t.throws(f([]), err, 'throws if function returns an array')
t.throws(f({}), err, 'throws if function returns an object')

const lift =
x => x !== undefined ? Maybe.Just(x) : Maybe.Nothing()

const good = maybeToList(lift, some)
const bad = maybeToList(lift, undefined)

t.ok(isSameType(List, good), 'returns a List with a Just')
t.ok(isSameType(List, bad), 'returns a List with a Nothing')

t.ok(good.equals(List.of(some)), 'Just maps to single item List containing the value')
t.ok(bad.equals(List.empty()), 'Nothing maps to an empty List')

t.end()
})
35 changes: 35 additions & 0 deletions src/Maybe/maybeToArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/** @license ISC License (c) copyright 2019 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */

const Maybe = require('.')

const curry = require('../core/curry')
const isFunction = require('../core/isFunction')
const isSameType = require('../core/isSameType')

const applyTransform = maybe =>
maybe.either(() => [], x => [ x ])

const err =
'maybeToArray: Argument must be a Maybe instanstace or a Maybe returning function'

function maybeToArray(maybe) {
if(isFunction(maybe)) {
return function(x) {
const m = maybe(x)

if(!isSameType(Maybe, m)) {
throw new TypeError(err)
}
return applyTransform(m)
}
}

if(isSameType(Maybe, maybe)) {
return applyTransform(maybe)
}

throw new TypeError(err)
}

module.exports = curry(maybeToArray)
82 changes: 82 additions & 0 deletions src/Maybe/maybeToArray.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const test = require('tape')
const helpers = require('../test/helpers')

const bindFunc = helpers.bindFunc

const Maybe = require('../core/Maybe')

const isArray = require('../core/isArray')
const isFunction = require('../core/isFunction')

const identity = x => x

const maybeToArray = require('./maybeToArray')

test('maybeToArray transform', t => {
const f = bindFunc(maybeToArray)

t.ok(isFunction(maybeToArray), 'is a function')

const err = /maybeToArray: Argument must be a Maybe instanstace or a Maybe returning function/
t.throws(f(undefined), err, 'throws if second arg is undefined')
t.throws(f(null), err, 'throws if second arg is null')
t.throws(f(0), err, 'throws if second arg is a falsey number')
t.throws(f(1), err, 'throws if second arg is a truthy number')
t.throws(f(''), err, 'throws if second arg is a falsey string')
t.throws(f('string'), err, 'throws if second arg is a truthy string')
t.throws(f(false), err, 'throws if second arg is false')
t.throws(f(true), err, 'throws if second arg is true')
t.throws(f([]), err, 'throws if second arg is an array')
t.throws(f({}), err, 'throws if second arg is an object')

t.end()
})

test('maybeToArray with Maybe', t => {
const some = 'something'

const good = maybeToArray(Maybe.Just(some))
const bad = maybeToArray(Maybe.Nothing())

t.ok(isArray(good), 'returns an Array with a Just')
t.ok(isArray(bad), 'returns an Array with a Nothing')

t.same(good, [ some ], 'Just maps to single item Array containing the value')
t.same(bad, [], 'Nothing maps to an empty Array')

t.end()
})

test('maybeToArray with Maybe returning function', t => {
const some = 'something'

t.ok(isFunction(maybeToArray(Maybe.of)), 'returns a function')

const f = bindFunc(maybeToArray(identity))

const err = /maybeToArray: Argument must be a Maybe instanstace or a Maybe returning function/
t.throws(f(undefined), err, 'throws if function returns undefined')
t.throws(f(null), err, 'throws if function returns null')
t.throws(f(0), err, 'throws if function returns a falsey number')
t.throws(f(1), err, 'throws if function returns a truthy number')
t.throws(f(''), err, 'throws if function returns a falsey string')
t.throws(f('string'), err, 'throws if function returns a truthy string')
t.throws(f(false), err, 'throws if function returns false')
t.throws(f(true), err, 'throws if function returns true')
t.throws(f([]), err, 'throws if function returns an array')
t.throws(f({}), err, 'throws if function returns an object')

const lift =
x => x !== undefined ? Maybe.Just(x) : Maybe.Nothing()

const good = maybeToArray(lift, some)
const bad = maybeToArray(lift, undefined)

t.ok(isArray(good), 'returns an Array with a Just')
t.ok(isArray(bad), 'returns an Array with a Nothing')

t.same(good, [ some ], 'Just maps to single item Array containing the value')
t.same(bad, [], 'Nothing maps to an empty Array')

t.end()
})
4 changes: 2 additions & 2 deletions src/Result/maybeToResult.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test('maybeToResult with Maybe', t => {
t.ok(isSameType(Result, bad), 'returns an Result with a Nothing')

t.equals(good.either(identity, identity), some, 'Just maps to a Ok')
t.equals(bad.either(identity, identity), none, 'Nothing maps to a Left with option value')
t.equals(bad.either(identity, identity), none, 'Nothing maps to a Err with option value')

t.end()
})
Expand Down Expand Up @@ -80,7 +80,7 @@ test('maybeToResult with Maybe returning function', t => {
t.ok(isSameType(Result, bad), 'returns an Result with a Nothing')

t.equals(good.either(identity, identity), some, 'Just maps to a Ok')
t.equals(bad.either(identity, identity), none, 'Nothing maps to a Left with option value')
t.equals(bad.either(identity, identity), none, 'Nothing maps to an Err with option value')

t.end()
})

0 comments on commit 17a85ff

Please sign in to comment.