Inconsistent do notation in functions

287 views Asked by At

Why is this function allowed:

-- function 1
myfunc :: String
myfunc = do
  x <- (return True)
  show x

and this is not:

-- function 2
myfunc :: String
myfunc = do
  x <- getLine
  show x

The compile error:

Couldn't match type `[]' with `IO'
Expected type: IO Char
Actual type: String

I get why function 2 shouldn't work, but why then thus function 1 work?

and why does this then work:

-- function 3
myfunc = do
  x <- getLine
  return (show x)

I get that it returns IO String then, but why is function 1 also not forced to do this?

2

There are 2 answers

18
Sri Kadimisetty On BEST ANSWER

In function1 the do block in myfunc is working in the list monad, because String is really just [Char]. In there, return True just creates [True]. When you do x <- return True that "extracts" True out of [True] and binds it to x. The next line show x converts True into a String "True". which being the return value the compiler value expects to see, ends up working fine.

Meanwhile in function2, the do block in myfunc is also working on the list monad (for the same reason, String being really [Char]) but calls on getLine which is only available in the IO monad. So unsurprisingly, this fails.

-- EDIT 1

OP has added a function3

-- function 3
myfunc :: String
myfunc = do
  x <- getLine
  return (show x)

No this should not work for the same reason function2 fails.

-- EDIT 2

OP has updated function3 to fix a copy paste error.

-- function 3
myfunc = do
  x <- getLine
  return (show x)

This is mentioned in the comments, but for clarity sake, this works because, when the type information is unspecified, GHC makes it best inference and after seeing getLine, it figures it’s IO String which does provide getLine.

Note - I wrote this answer with as casual a tone as I could manage without being wrong with the intention of making it approachable to a beginner level.

3
dfeuer On

do blocks work in the context of an arbitrary Monad. The Monad, in this case, is []. The Monad instance for lists is based on list comprehensions:

instance Monad [] where
  return x = [x]
  xs >>= f = [y | x <- xs, y <- f x]

You can desugar the do notation thus:

myfunc :: String
myfunc = do
  x <- (return True)
  show x

-- ==>

myfunc = [y | x <- return True, y <- show x]

-- ==>

myfunc = [y | x <- [True], y <- show x]

In a list comprehension, x <- [True] is really just the same as let x = True, because you're only drawing one element from the list. So

myfunc = [y | y <- show True]

Of course, "the list of all y such that y is in show True" is just show True.