Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell list monad and return ()

Tags:

haskell

Consider the following piece of code:

foo :: [Int]
foo = do
  x <- [1..10]
  if x < 5 then pure () else [] -- Control.Monad.guard (x < 5)
  pure x

foo2 :: [Int]
foo2 = 
  [1..10] >>= \x ->
    if x < 5 then pure x else [] >>= \y ->
      pure y

In foo, I have manually inlined Control.Monad.guard (x < 5), as noted in the comment.

Why does foo compile, even though there is pure () in the code? How does the [()] pass the type check? Is it a special case for the do syntax? If yes, is it described anywhere?

In foo2, I attempted to "desugar" foo without the do syntax. Note that there can't be any pure (), as it doesn't pass type checking.

I'm using ghc-8.8.4 if that's important.

like image 328
Jan Synáček Avatar asked Oct 14 '20 07:10

Jan Synáček


People also ask

Is Haskell list a monad?

Lists are a fundamental part of Haskell, and we've used them extensively before getting to this chapter. The novel insight is that the list type is a monad too! As monads, lists are used to model nondeterministic computations which may return an arbitrary number of results.

Is list a monad?

List as a data structure is not a Monad, but the fact that Scala's List implements flatMap is what gives it its monadic super-powers. It also needs to fulfil associativity, left unit and right unit laws to qualify as a Monad.

What is Haskell monad function?

What is a Monad? A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

What is return Haskell?

return is actually just a simple function in Haskell. It does not return something. It wraps a value into a monad.


1 Answers

You have several errors in your manual desugaring. One attempt that uses only >>= would be:

foo2 :: [Int]
foo2 =
  [1..10] >>= \x ->
    (if x < 5 then pure () else []) >>= \_ ->
      pure x

First, the parentheses are important: you're binding the result of the entire if expression, not performing a bind inside of its else branch. Second, you can't just introduce a new variable y and then use it in your pure result. Desugaring preserves the same expressions in your source code, it just moves them around a bit. So, pure x must desugar to pure x.

Hopefully you can see why this works: the type of () does not matter, because nobody ever looks at its value, and the result, pure x, has the right type regardless.

But really GHC doesn't produce quite this code: x >>= \_ -> y is equivalent to x >> y, and that's what's used for a statement in a do-block that does not bind its result to a variable. So really you get

foo2 :: [Int]
foo2 =
  [1..10] >>= \x ->
    (if x < 5 then pure () else []) >> pure x

And you can, if you prefer, use a slightly fancier operator from Functor to achieve the same result. Let's un-inline guard, and use (<$) :: Functor f => a -> f b -> f a instead of a monadic operation. x <$ y is the same as y >> pure x:

foo2 :: [Int]
foo2 =
  [1..10] >>= \x ->
    x <$ guard (x < 5)
like image 133
amalloy Avatar answered Oct 23 '22 14:10

amalloy