MostlyAdequate / mostly-adequate-guide

Mostly adequate guide to FP (in javascript)
Other
23.39k stars 1.86k forks source link

show encapsulated types via inspect() #607

Open dotnetCarpenter opened 3 years ago

dotnetCarpenter commented 3 years ago

In order to understand the examples in this book, I usually console.log the compositions in node but whenever an ADT encapsulates another ADT, I get confusing output. I sometimes thought I was looking at a bug but this PR will fix that :)

Not only that but it will fix a case that currently will throw a TypeError.

// Without this PR, the following will throw:
// TypeError: Cannot convert undefined or null to object
console.log(
    new List([Maybe.of(1), Maybe.of(null)]), // List([Just(1), Nothing])
)

Here is what I used to test the improved inspect function with:

'use strict'

const { Identity, Either, List, Left, Right, Maybe, Task, Map, IO,
    curry, compose, sequence, map, traverse } = require('@mostly-adequate/support')

const identity1 = compose(sequence(Identity.of), map(Identity.of))
const identity2 = Identity.of

// fromPredicate :: (a -> Bool) -> a -> Either e a
const fromPredicate = curry((predicate, a) => predicate(a)
    ? new Right(a)
    : new Left("predicate failed"))

// partition :: (a -> Bool) -> [a] -> [Either e a]
const partition = f => map(fromPredicate(f))

// validate :: (a -> Bool) -> [a] -> Either e [a]
const validate = f => traverse(Either.of, fromPredicate(f))

// isEven :: Number -> Boolean
const isEven = n => n % 2 === 0

const array = [1,2,3,4,5]

console.log(
    // test it out with Right
    identity1(Either.of('stuff')), // Requires #605 - missing return in Right.traverse
    // Identity(Right('stuff'))

    identity2(Either.of('stuff')),
    // Identity(Right('stuff'))
)

console.log(
    partition(isEven)(new List(array)),
    // List([Left('predicate failed'), Right(2), Left('predicate failed'), Right(4), Left('predicate failed')])
)

console.log(
    Either.of(Identity.of('stuff')), // Right(Identity('stuff'))
    Maybe.of(IO.of('something')), // Just(IO(?))
)

Currently the output will be:

undefined Identity({$value: 'stuff'})
List([{$value: 'predicate failed'}, {$value: 2}, {$value: 'predicate failed'}, {$value: 4}, {$value: 'predicate failed'}])
Right({$value: 'stuff'}) Just({unsafePerformIO: () => x})

Contrast that with the following, with this patch applied:

undefined Identity(Right('stuff'))
List([Left('predicate failed'), Right(2), Left('predicate failed'), Right(4), Left('predicate failed')])
Right(Identity('stuff')) Just(IO(?))

If both PR #605 and #606 is applied then the following is also possible (and identity1(Either.of('stuff')) is no longer undefined):

// Requires #606 - foldable aware traverse
console.log(
    validate(isEven)(array) // Left('predicate failed')
)

// Requires #605 - Pointed Map
console.log(
    Either.of(Map.of({ a: Task.of(1), b: Task.of(2) })), // Right(Map({a: Task(?), b: Task(?)}))
)

The output is now:

Identity(Right('stuff')) Identity(Right('stuff'))
List([Left('predicate failed'), Right(2), Left('predicate failed'), Right(4), Left('predicate failed')])
Right(Identity('stuff')) Just(IO(?))
Left('predicate failed')
Right(Map({a: Task(?), b: Task(?)}))
List([Just(1), Nothing])

That is all of the ADT's except Compose which is in a closure, so I can not pattern match on the Compose constructor at the moment.