functional-programmingmonadselmoption-typesanctuary

How should I map over Maybe List?


I came away from Professor Frisby's Mostly Adequate Guide to Functional Programming with what seems to be a misconception about Maybe.

I believe:

map(add1, Just [1, 2, 3])
// => Just [2, 3, 4]

My feeling coming away from the aforementioned guide is that Maybe.map should try to call Array.map on the array, essentially returning Just(map(add1, [1, 2, 3]).

When I tried this using Sanctuary's Maybe type, and more recently Elm's Maybe type, I was disappointed to discover that neither of them support this (or, perhaps, I don't understand how they support this).

In Sanctuary,

> S.map(S.add(1), S.Just([1, 2, 3]))
! Invalid value

add :: FiniteNumber -> FiniteNumber -> FiniteNumber
                       ^^^^^^^^^^^^
                            1

1)  [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber

The value at position 1 is not a member of ‘FiniteNumber’.

In Elm,

> Maybe.map sqrt (Just [1, 2, 3])
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm

The 2nd argument to function `map` is causing a mismatch.

4|   Maybe.map sqrt (Just [1, 2, 3])
                     ^^^^^^^^^^^^^^
Function `map` is expecting the 2nd argument to be:

    Maybe Float

But it is:

    Maybe (List number)

Similarly, I feel like I should be able to treat a Just(Just(1)) as a Just(1). On the other hand, my intuition about [[1]] is completely the opposite. Clearly, map(add1, [[1]]) should return [NaN] and not [[2]] or any other thing.

In Elm I was able to do the following:

> Maybe.map (List.map (add 1)) (Just [1, 2, 3])
Just [2,3,4] : Maybe.Maybe (List number)

Which is what I want to do, but not how I want to do it.

How should one map over Maybe List?


Solution

  • As Chad mentioned, you want to transform values nested within two functors.

    Let's start by mapping over each individually to get comfortable:

    > S.map(S.toUpper, ['foo', 'bar', 'baz'])
    ['FOO', 'BAR', 'BAZ']
    
    > S.map(Math.sqrt, S.Just(64))
    Just(8)
    

    Let's consider the general type of map:

    map :: Functor f => (a -> b) -> f a -> f b
    

    Now, let's specialize this type for the two uses above:

    map :: (String -> String) -> Array String -> Array String
    
    map :: (Number -> Number) -> Maybe Number -> Maybe Number
    

    So far so good. But in your case we want to map over a value of type Maybe (Array Number). We need a function with this type:

    :: Maybe (Array Number) -> Maybe (Array Number)
    

    If we map over S.Just([1, 2, 3]) we'll need to provide a function which takes [1, 2, 3]—the inner value—as an argument. So the function we provide to S.map must be a function of type Array (Number) -> Array (Number). S.map(S.add(1)) is such a function. Bringing this all together we arrive at:

    > S.map(S.map(S.add(1)), S.Just([1, 2, 3]))
    Just([2, 3, 4])