Folktale / fantasyland Maybe not working as expected

309 views Asked by At

Reading Frisbys guide to functional programming, currently on the chapter about Maybe. In the appendix the book suggests using either folktale or fantasyland.

However in both libraries Maybe doesn't seem to be work as described in the book.

const Maybe = require('folktale/maybe')
// const Maybe = require('fantasy-options')
const {
    flip, concat, toUpper, path, pathOr, match, prop
} = require('ramda')

console.log(
    Maybe.of('Malkovich Malkovich').map(match(/a/ig))
)
// Just(['a', 'a'])

Maybe.of(null).map(match(/a/ig))
//******************************
// TypeError: Cannot read property 'match' of null
//******************************
// Nothing

Maybe.of(
    { name: 'Boris' }
).map(prop('age')).map(add(10))
// Nothing

Maybe.of(
    { name: 'Dinah', age: 14 }
).map(prop('age')).map(add(10))
// Just(24)

In this example copied from the book, the first statement works correctly, but the second gets a TypeError. This seems to completely go against the purpose of Maybe. Or am I misunderstanding something?

Example repl.it

1

There are 1 answers

1
arcseldon On

Update: August 2019

Pleased you asked this question, I was also surprised initially at the difference in behaviour. As others have responded, it comes down to the way the Frisby Mostly Adequate Guide implementation was coded. The "irregular" implementation detail is related to the way in which isNothings function implementation is shielding the null or undefined value passed in using Maybe.of:

get isNothing() {
    return this.$value === null || this.$value === undefined;
 }

If you refer to other implementations - then using Maybe.of() to create your Maybe does allow you to pass in a null or undefined for the Just case's value and actually print for example Maybe.Just({ value: null })

Instead, when using Folktale, create the Maybe using Maybe.fromNullable() which will allocate a Just or Nothing according to the value input.

Here's a working version of the code provided:

const Maybe = require("folktale/maybe");

const {
  flip,
  concat,
  toUpper,
  path,
  pathOr,
  match,
  prop,
  add
} = require("ramda");

console.log(Maybe.of("Malkovich Malkovich").map(match(/a/gi)));
//-> folktale:Maybe.Just({ value: ["a", "a"] })

console.log(Maybe.fromNullable(null).map(match(/a/gi)));
//-> folktale:Maybe.Nothing({  })

Finally, here's a demonstration implementation of Maybe, codified to use fromNullable (similar to the Folktale implementation). I took this reference implementation from what I consider a highly recommended book - Functional Programming In JavaScript by Luis Atencio. He spends much of Chapter 5 explaining this clearly.

/**
 * Custom Maybe Monad used in FP in JS book written in ES6
 * Author: Luis Atencio
 */ 
exports.Maybe = class Maybe {
    static just(a) {
        return new exports.Just(a);
    }
    static nothing() {
        return new exports.Nothing();
    }
    static fromNullable(a) {
        return a !== null ? Maybe.just(a) : Maybe.nothing();
    }
    static of(a) {
        return Maybe.just(a);
    }
    get isNothing() {
        return false;
    }
    get isJust() {
        return false;
    }
};


// Derived class Just -> Presence of a value
exports.Just = class Just extends exports.Maybe {
    constructor(value) {
        super();
        this._value = value;
    }

    get value() {
        return this._value;
    }

    map(f) {
        return exports.Maybe.fromNullable(f(this._value));
    }

    chain(f) {
        return f(this._value);
    }

    getOrElse() {
        return this._value;
    }

    filter(f) {
        exports.Maybe.fromNullable(f(this._value) ? this._value : null);
    }

    get isJust() {
        return true;
    }

    toString () {
        return `Maybe.Just(${this._value})`;
    }
};

// Derived class Empty -> Abscense of a value
exports.Nothing = class Nothing extends exports.Maybe {
    map(f) {
        return this;
    }

    chain(f) {
        return this;
    }

    get value() {
        throw new TypeError("Can't extract the value of a Nothing.");
    }

    getOrElse(other) {
        return other;
    }

    filter() {
        return this._value;
    }

    get isNothing() {
        return true;
    }   

    toString() {
        return 'Maybe.Nothing';
    }
};