3

Here is a piece of code from the book "Learn You a Haskell for Great Good!" by Miran Lipovača:

addStuff :: Int -> Int
addStuff = do
 a <- (*2)
 b <- (+10)
 return (a+b)

What is bound to the parameter b in the expression b <- (+10) in the do-expression? If it were a value like Just 7 or [7], the seven would get bound; if it were a call of getLine, then it would be a string. What about functions? Or is it incorrect to view such instructions with functions in terms of extracting and wrapping something? And a more general question. If monadic values are values with context, then what is considered the context in the case of functions?

asked Mar 20 at 1:05
4
  • 1
    For a function Int -> Int, the (Int ->) is the context, the Int is the value. But really "monadic values are values with context" doesn't make much sense in many cases. It's the type (or structure) that is monadic, and what a concrete value means depends very much on that type. Commented Mar 20 at 1:14
  • 2
    Try rewriting the do notation to a >>= chain, and then look up the implementation of >>= and return for functions. Commented Mar 20 at 1:19
  • Even in Maybe, it's not that simple. What does x <- Nothing bind to x? Then answer is "nothing" (not Nothing); the very idea of x having a value is irrelevant, because whatever logic that would have used x is ignored and replaced itself by Nothing. Commented Mar 21 at 11:11
  • @chepner I think we don't even need to ask the question "What does x <- Nothing bind to x?" If the bind operator (>>=) gets Nothing (or an empty list, for example) it "doesn't try to extract and bind" anything, it just passes the Nothing onward, probably for the next bind operator to deal with it. Commented Mar 21 at 14:25

2 Answers 2

9

If it were a value like Just 7 or [7], the seven would get bound

Okay, sure. But what if it were a value like [7,8,9]? Then there's no good, single answer to what value gets bound. In a somewhat related objection, you say "if it were a call of getLine, then it would be a string" -- but which string? There's no good, single answer to which string gets bound when I write b <- getLine, because the value that b takes on will depend on something the user does. So you will have to abandon this idea for understanding monads, and admit that they can have more structure and excitement than simply binding predictable values.

We are in the function monad case, and are defining a function named addStuff. The more exciting, structured answer to "what gets bound?" is that the value that gets bound depends on what value gets provided as an argument to addStuff. For each possible input that could be provided, there is a corresponding value that will appear in b. If addStuff is applied to 73, then b will be bound to 73+10=83; or if addStuff is applied to -5, then b will be bound to -5+10=5.

If monadic values are values with context, then what is considered the context in the case of functions?

In the case of functions, the argument to the function is the context.

answered Mar 20 at 4:09
Sign up to request clarification or add additional context in comments.

Comments

3

What is bound to the parameter b

The result of the function (at the right of the arrow, so here (+10)) and the input to the function addStuff, so if you call addStuff 5, b will be 15.

Indeed, if we desugar the do block, what you wrote is:

addStuff :: Int -> Int
addStuff = (*2) >>= \a -> ((+10) >>= \b -> return (a + b))

For the function instance of a Monad, we have:

-- | @since base-2.01
instance Applicative ((->) r) where
pure = const
f g x = f x (g x)
liftA2 q f g x = q (f x) (g x)
-- | @since base-2.01
instance Monad ((->) r) where
f >>= k = \r -> k (f r) r

so that means for addStuff, it looks like:

addStuff = \r -> (\a -> (+10) >>= \b -> return (a+b)) ((*2) r) r
addStuff = \r -> (\a -> (\s -> (\b -> const (a+b)) ((+10) s) s)) ((*2) r) r

We can now do some function applications reducing the "noise":

addStuff = \r -> (\a -> (\s -> (\b -> const (a+b)) ((+10) s) s)) ((*2) r) r
addStuff = \r -> (\b -> const (((*2) r) +b)) ((+10) r) r
addStuff = \r -> (const (((*2) r) +((+10) r))) r
addStuff = \r -> (((*2) r) +((+10) r))

it thus each time passes the argument r further, and the return on the last line ignores the parameter.

We could have used:

addStuff :: Int -> Int
addStuff = do
 a <- (*2)
 b <- (+10)
 ((a+b)+)

This would have added the input to the a+b part, so addStuff 5 is then 30 since we first multiply 5 with 2 for a, then 5+10 for b, and finally we add the input a third time.

Daniel Wagner
156k10 gold badges231 silver badges392 bronze badges
answered Mar 21 at 7:50

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.