2

In this tutorial http://learnyouahaskell.com/starting-out the author writes this piece of code.

boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]

And then later executes it like this

boomBangs[7..13]

And my question is, what does the "<-" operator do? To me this seems like it would cause recursive behavior, since I am referencing what looks to me like a function inside a function, or perhaps defining how to create a list comprehension.

Searching around I found this explanation by chi on a different question:
"x <- action runs the IO action, gets its result, and binds it to x"

Is the "<-" in the question linked above different to the "<-" used in the code I copied above? Does xs run inside xs? I'd be grateful if someone could explain to me what's going on here.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • List comprehension syntax is basically just `do` expresssion syntax, which is syntactical sugar for some expressions with binds. – Willem Van Onsem Aug 12 '19 at 10:16
  • 1
    The `xs` on the LHS is the argument of the function, and on the RHS it is the actual function definition. It's no different than `f x = x + 3`. In this case, the `<-` operator just means to take successive values from the `xs` list and call them each `x` as you go through the loop. – Andrew Jaffe Aug 12 '19 at 10:17
  • 8
    `<-` isn't really an operator; it's just part of the syntax of a list comprehension. – chepner Aug 12 '19 at 11:01
  • yes, the "<-" in the question linked above (part of monadic [tag:do-notation] syntax) *is* different to the "<-" used in the code (part of list comprehension syntax). No, xs does not "run inside xs", it is meaningless to say this. `xs` on the right of `=` (a variable) refers to the `xs` on the left (i.e. `boomBangs`'s parameter). – Will Ness Aug 12 '19 at 15:59
  • Thanks everyone! Learned a bunch of new things, appreciate it! – Magnar Kleppe Aug 13 '19 at 06:39

1 Answers1

1

Your list comprehension is in essence just syntactical sugar for:

import Control.Monad(guard)

boomBangs :: Integral i => [i] -> [String]
boomBangs xs = do
    x <- xs
    guard (odd x)
    return (if x < 10 then "BOOM!" else "BANG!")

This is thus a do expression [Haskell report], and as the report says, it is syntactical sugar. It is syntactical sugar for:

boomBangs xs = xs >>= \x -> (guard (odd x) >> return (if x < 10 then "BOOM!" else "BANG!"))

For a list the Monad instance is defined as:

instance Monad [] where
    (>>=) = flip concatMap
    return x = [x]

Furthermore the guard is defined as:

guard :: Monad m => Bool -> m ()
guard True = pure ()
guard False = empty

and the default implementation of (>>) is:

(>>) :: Monad m => m a -> m b -> m b
(>>) u v = u >>= \_ -> v

So the boomBangs is basically implemented as:

boomBangs xs = concatMap (\x -> (guard (odd x) >>= \_ -> [if x < 10 then "BOOM!" else "BANG!"])) xs
             = concatMap (\x -> concatMap (\_ -> [if x < 10 then "BOOM!" else "BANG!"]) guard (odd x)) xs

Since for a list, the guard can be specialized to:

-- guard for the Monad []
guard :: Bool -> [()]
guard True = [()]
guard False = []

It thus means that if the guard gets a True, it returns a singleton list, and for False an empty list. This thus means that given the guard holds, the concatMap (\_ -> [if x < 10 then "BOOM!" else "BANG!"]) will return the content in the [if x < 10 then "BOOM!" else "BANG!"], if the guard fails, it will return an empty list. The guard acts thus as some sort of filter.

So now what is the x <-. If we look at how the do-expressions are desugared, an x <- foo, corresponds to the foo >>= \x -> ....

For list comprehensions, x <- ... acts as some sort of "enumerator": it will enumerate over all elements in the list, and x will each time obtain one of the elements in the list to do further processing.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555