Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding this assignment?

In order to refresh my 20 year old experience with Haskell I am walking through https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Adding_Variables_and_Assignment and at one point the following line is introduced to apply op to all the params. This is to implement e.g. (+ 1 2 3 4)

numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op

I do not understand the syntax, and the explanation in the text is a bit vague.

I understand what foldl1 does and how to dot functions (unpackNum is a helper function), but using Monads and the >>= operator leaves me a bit confused.

How is this to be read?

like image 307
Thorbjørn Ravn Andersen Avatar asked Jun 12 '18 21:06

Thorbjørn Ravn Andersen


People also ask

What is understanding the assignment?

The slang term is a popular way to praise someone who is going above and beyond to do a good job. According to Urban Dictionary, "understood the assignment" means, "a phrase used when someone is giving it 110% ... Whether it's what they're doing, what they're wearing, someone who is really on top of their s***" .

Why is it important to understand the writing assignment?

The assignment may require you to persuade your reader, compare and contrast ideas, or summarize an author's point of view. Considering your purpose at this point will make it easier for you to figure out what kind of thesis you'll need when you start to write the paper.


2 Answers

Essentially,

mapM unpackNum params >>= return . Number . foldl1 op

is made of two parts.

mapM unpackNum params means: take the list of parameters params. On each item, apply unpackNum: this will produce an Integer wrapped inside the ThrowsError monad. So, it's not exactly a plain Integer, since it has a chance to error out. Anyway, using unpackNum on each item either successfully produces all Integers, or throws some error. In the first case, we produce a new list of type [Integer], in the second one we (unsurprisingly) throw the error. So, the resulting type for this part is ThrowsError [Integer].

The second part is ... >>= return . Number . foldl1 op. Here >>= means: if the first part threw some error, the whole expression throws that error as well. If the part succeeded in producing [Integer] then proceed with foldl1 op, wrap the result as a Number, and finally use return to inject this value as a successful computation.

Overall there are monadic computations, but you should not think about those too much. The monadic stuff here is only propagating the errors around, or store plain values if the computation is successful. With a bit of experience, one can concentrate on the successful values only, and let mapM,>>=,return handle the error cases.

By the way, note that while the book uses code like action >>= return . f, this is arguably a bad style. One can use, to the same effect, fmap f action or f <$> action, which is a more direct way to express the same computation. E.g.

Number . foldl1 op <$> mapM unpackNum params

which is very close to a non-monadic code which ignores the error cases

-- this would work if there was no monad around, and errors did not have to be handled
Number . foldl1 op $ map unpackNum params
like image 60
chi Avatar answered Sep 18 '22 23:09

chi


Your question is about syntax, so I'll just talk about how to parse that expression. Haskell's syntax is pretty simple. Informally:

  • identifiers separated by spaces are function application (the first identifier applied to the rest)
  • except identifiers that use non-alphanumeric chatracters (e.g. >>=, or .) are infix (i.e. their first argument is to the left of the identifier)
  • the first type of function application above (non-infix) binds more tightly than the second
  • operators can associate either to the left or right, and have different precedence (defined with an infix... declaration)

So only knowing this, if I see:

mapM unpackNum params >>= return . Number . foldl1 op

To begin with I know that it must be parse like

(mapM unpackNum params) >>= return . Number . (foldl1 op)

To go further we need to inspect the fixity/precedence of the two operators we see in this expression:

Prelude> :info (.)
(.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in ‘GHC.Base’
infixr 9 .
Prelude> :info (>>=)
class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  ...
    -- Defined in ‘GHC.Base’
infixl 1 >>=

(.) has a higher precedence (9 vs 1 for >>=), so its arguments will bind more tightly (i.e. we parenthesize them first). But how do we know which of these is correct?

(mapM unpackNum params) >>= ((return . Number) . (foldl1 op))
(mapM unpackNum params) >>= (return . (Number . (foldl1 op)))

...? Because (.) was declared infixr it associates to the right, meaning the second parse above is correct.

As Will Ness points out in the comments, (.) is associative (like e.g. addition) so both of these happen to be semantically equivalent.

With a little experience with a library (or the Prelude in this case) you learn to parse expressions with operators correctly without thinking too much.

If after doing this exercise you want to understand what a function does or how it works, then you can click through to the source of the functions you're interested in and replace occurrences of left-hand sides with right-hand sides (i.e. inline the bodies of the functions and terms). Obviously you can do this in your head or in an editor.

like image 36
jberryman Avatar answered Sep 20 '22 23:09

jberryman