3

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?

Ziggy
  • 21,845
  • 28
  • 75
  • 104
  • 1
    in Haskell there's a Functor instance defined for the [*composition of two Functors*](https://www.stackage.org/haddock/lts-9.0/base-4.9.1.0/Data-Functor-Compose.html), so that `fmap` works two levels deep, inside the composed Functor. After `import Data.Functor.Compose`, `getCompose $ fmap (1+) $ Compose $ Just [1,2,3]` returns `Just [2,3,4]`. `Compose`/`getCompose` are just helpers for the type system. – Will Ness Aug 03 '17 at 21:21
  • Haskell has everything, eh!? – Ziggy Aug 06 '17 at 02:08

2 Answers2

7

You have two functors to deal with: Maybe and List. What you're looking for is some way to combine them. You can simplify the Elm example you've posted by function composition:

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

This is really just a short-hand of the example you posted which you said was not how you wanted to do it.

Sanctuary has a compose function, so the above would be represented as:

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

Similarly, I feel like I should be able to treat a Just(Just(1)) as a Just(1)

This can be done using the join from the elm-community/maybe-extra package.

join (Just (Just 1)) == Just 1
join (Just Nothing)  == Nothing
join Nothing         == Nothing

Sanctuary has a join function as well, so you can do the following:

S.join(S.Just(S.Just(1))) == Just(1)
S.join(S.Just(S.Nothing)) == Nothing
S.join(S.Nothing)         == Nothing
Chad Gilbert
  • 36,115
  • 4
  • 89
  • 97
5

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])
davidchambers
  • 23,918
  • 16
  • 76
  • 105
  • Hey! You're the guy! :) God bless stackoverflow. – Ziggy Aug 03 '17 at 23:42
  • Months later, I get it! The function `add1` is `number -> number`, and map lifts add1 into `Maybe number -> Maybe number`. I was trying to apply a function that accepts and argument of `Maybe number` to a value of type `List number`. That won't work! In your last example you first lift `add1` to be `F number -> F number` and then lift it to be `Maybe F number -> Maybe F number`, and finally apply it to a value of type `Maybe F number`. – Ziggy Nov 24 '17 at 18:49
  • 1
    It's really cool that I can use `S.map(S.add(1))` to create a function `F number -> F number` and then apply that one function to both `S.Just(1)` and `[1, 2, 3]` even though they have different types! One is `Maybe number` and `List number`. The magic of polymorphism! – Ziggy Nov 24 '17 at 18:51