2

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

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
Bill Johnston
  • 1,160
  • 1
  • 14
  • 31
  • 1
    `Maybe.of` always constructs a `Maybe`. Perhaps look at `Maybe.fromNullable` which constructs either a `Maybe` or a `Nothing` depending on the input – Mulan May 24 '18 at 15:07
  • 1
    `Maybe` isn't able to replace `null` on the language level. However, `Maybe` has a similar role to `null` and hence you can replace it on the application level. `null` wrapped in a `Maybe` is still `null` and will cause type errors for lifted functions. –  May 24 '18 at 15:55
  • @ftor yes, but Isn't it supposed to skip running the .map fn if its value is null? `map(fn) { return this.isNothing ? this : Maybe.of(fn(this.$value)); }` I thought this was the entire purpose of `Maybe` – Bill Johnston May 24 '18 at 16:02
  • You can construct a type that has certain behavior for `null`. But then you lose parametric polymorphism for this type. I am not experienced enough to assess the consequences, though. Maybe it is not too bad to make an exception for `null`. Anyway, look into parametricity. –  May 24 '18 at 16:29
  • 3
    @BillJohnston The whole point of `Maybe` is to provide a more elegant _alternative_ to `null`, not a wrapper for `null` values. It's not `Maybe`'s job to recognize `null` and turn them into `Nothing`s. Frisby's book uses the (somewhat hacky) approach of treating a `Maybe` containing a `null` as `Nothing`, but I don't think that's typical and that's pretty much an implementation detail. In Folktale, the way to create `Nothing` is to use `Maybe.Nothing()`, but Folktale also provides `Maybe.fromNullable` (already mentioned above) which turns `null` and `undefined` into `Nothing`. – JLRishe May 27 '18 at 15:31

1 Answers1

2

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';
    }
};
arcseldon
  • 35,523
  • 17
  • 121
  • 125
  • 1
    Nice answer! Can you also tell something about this case: `Maybe.of("foo").map(s => null).map(s => s.toUpperCase())` (assuming `s => null` is a function that can return either a string or a null). My intuition was that if a function in the chain of `map`s returns a null, the chain must stop. But now I see I was wrong. Still, I'm wondering, is there an idiomatic way to stop the chain in such cases? I tried to return `Maybe.Nothing()` instead of `null`, but it just wraps `Nothing` into `Just`, so the upper-case function gets instance of `Nothing`. – Anatoliy Arkhipov Oct 11 '19 at 01:31