Pass more than 1 parameter to monad

Viewed 625

I'm learning Haskell and making up some examples. I'm not sure why the second example doesn't work

foo :: Int -> Int -> Maybe Int
foo 0 0 = Nothing
foo a b = Just $ a + b

bar :: Int -> Maybe Int
bar 0 = Nothing
bar a = Just $ a + 1

-- This works
Just 4 >>= bar

-- Why this doesn't work?
(Just 4 Just 4) >>= foo

-- This works
do
    a <- Just 3
    b <- Just 4
    foo a b
3 Answers

As the comment says, (Just 4 Just 4) tries to apply the constructor Just to 3 arguments when it only takes one. So, I will assume that you wanted something like (Just 4, Just 4), and want it to work like your final example.

The type of the "bind" operator is (>>=) :: Monad m => m a -> (m a -> b) -> m b. This means that the function expected after the operator only takes one argument, not two. So, again, the ultimate reason why it doesn't work is that, your function takes the wrong number of arguments. (Partial application means that you don't have to provide all the arguments at once, but it sounds like you're expecting some other piece of data to be magically routed to the missing argument...)

Desugaring your do example to >>= form translates as:

Just 3 >>= \a -> Just 4 >>= \b -> foo a b

To make this a little clearer, I'll parenthesize the lambdas:

Just 3 >>= ( \a -> Just 4 >>= (\b -> foo a b) )

That makes it easier to see that you can simplify the inner lambda:

Just 3 >>= ( \a -> Just 4 >>= foo a )

So, it's possible after all to route the missing data to the extra argument! But, you do have to work out the routing yourself...

There's nothing particularly magical about Haskell functions; they tend to be more particular about how they're called than dynamic languages. The largest "magic" here is that the type checker can often tell when you're not using them correctly.

And (as the other answer notes) there is nothing magical about >>= -- it's just another function, and in order to understand how to use it, you need to take a look at its type.

It doesn't work because >>= is a perfectly normal operator (and operators are perfectly normal functions).

You seem to be thinking of >>= as special syntax for getting values out of the monadic value on its left and feeding it to the function on the right. It is not special syntax; rather >>= itself is a function that gets applied to the values on its left and its right (and then computes a result as you expect).

However, that means that the left and right arguments must be valid expressions for things that could exist as ordinary values; things you could simply bind to variables with var = <expr> syntax. Just 4 >>= bar works because (among other requirements) Just 4 on its own is a valid expression of type Maybe Int and bar is a valid expression of type Int -> Maybe Int. Just 4 Just 4 >>= foo doesn't work because Just 4 Just 4 is not a correct expression (what would it's type be?); it's interpreted as applying Just to the 3 separate arguments 4, Just, and 4, whereas you want it to be two separate values Just 4 and Just 4. But even if you could get the compiler to interpret something there as two separate values, there's no way for >>= to be passed two separate values as its left argument; it's expecting (in this usage) a single value of type Just Int.

If you have a function like foo that needs two arguments and you want to source those arguments from values that are in a monadic context, then you can't just apply >>= you need to write code that does that (like your final example with the do block; there are many other ways to do something equivalent).

The other answers described why this doesn't work. But IMO it's quite reasonable that you want this, and indeed Just 3 >>= \x -> Just 4 >>= \y -> foo x y is a bit of a silly solution to the task. Basically, the x and y values are independent of each other, yet you're fetching them sequentially, in a way that the complete y calculation could in principle depend on the value of x.

Monads aren't really the right abstraction here, they're too strong. To get x and y non-sequentially, you can use Applicative interface. The form that most Haskellers prefer nowadays (I think) is

   foo <$> Just 3 <*> Just 4

You can read this as “zip the effectful values Just 3 and Just 4 together to a single action with two values, then apply foo over those values”.

...Actually that's not really how it works though, and for me that was super confusing when I first learned about applicatives. Namely, the above expression is in fact parsed as

   (foo <$> Just 3) <*> Just 4

which looks again like it's sequential-style. But it's not, what going on here is only a currying/laziness trick to pass multiple values through the applicative value without having to group them to a suitable tuple. The code that literally works like I explained it would be

  uncurry foo <$> ((,)<$>Just 3<*>Just 4)

Here, (,)<$>Just 3<*>Just 4 evaluates to Just (3,4). Then fmapping foo over that needs to be done in uncurried form, so the two arguments are accepted as a tuple. It's structurally clear, yet awkward because we're working against Haskell's curried style.

(Mathematically, this tupling is what's conceptually happing though: generally speaking, you're working in a monoidal category. Some other incarnations of applicative functors have such a tuppling-combinator as their underlying interface, instead of <*>; e.g. >*< from the invertible package.)

The trick with foo<$>Just 3<*>Just 4 is that instead of building a tuple, we start with partially applying foo to the 3 result. This doesn't actually require anything applicative/monadic yet – we're basically just transforming the contained value – in general: values – from 3 to foo 3, without touching their context. You may consider this a purely symbolic operation. Note that the type is Maybe (Int -> Int) at this point.
Then you use the <*> combinator to zip both of the Maybe contexts together, and simultaneously apply the foo 3 partially-evaluated function to its second argument.

I personally like this form, which is also equivalent:

  liftA2 foo (Just 3) (Just 4)

We're not finished yet though: all the above suggestions give a result of type Maybe (Maybe Int). To flatten that into a Maybe Int, that's where you actually need the monad interface. One option is

   join $ foo <$> Just 3 <*> Just 4
Related