Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the type match on the next line but not on the same line in `do` block?

I’m reading in several lines from the input that the user has to type:

main :: IO ()
main = do
  let size = 3
  arr <- replicateM size getLine
  let pairs = map parsePair arr
  print pairs

Why am I allowed to do map parsePair arr on a separate line but not on the same line, like this:

arr <- map parsePair (replicateM size getLine)

Doing so, I get the error :

• Couldn't match type ‘[]’ with ‘IO’
  Expected type: IO [Int]
    Actual type: [[Int]]

To give you more details, here is parsePair:

parsePair string = map parseInt $ words string

parseInt :: String -> Int
parseInt s = read s :: Int
like image 623
Albizia Avatar asked Dec 07 '22 11:12

Albizia


2 Answers

Because the type of replicateM size getLine is IO [String], it is not a list of Strings, it is basically a description of an IO action that will obtain a [String]. You can see the arrow <- in an IO monad as a way to retrieve it and unpack the result.

You can however do some processing on that like, since IO is a Functor as well, you can make use of fmap :: Functor f => (a -> b) -> f a -> f b:

main :: IO [Int]
main = do
  let size = 3
  fmap (map parsePair) (replicateM size getLine)

or you can shift the fmap to the getLine part:

main :: IO [Int]
main = do
  let size = 3
  replicateM size (fmap parsePair getLine)

Note that there is a readLn :: Read a => IO a function that is basically fmap read getLine (except that it does some addional error handling). We can thus use:

main :: IO [Int]
main = do
  let size = 3
  replicateM size readLn
like image 169
Willem Van Onsem Avatar answered Jan 14 '23 20:01

Willem Van Onsem


Note that the do syntax with the <- symbol is really not like the assignment operator (which is = in many languages, sometimes := and sometimes indeed <-, e.g. in R). Rather, it's a special construct that executes a monadic action and extracts the result of this action. There is is a fundamental distinction between an action and its result; and often-quoted analogy is that an action is like a recipe for a cake, the result is the cake itself.

applying map parsePair directly to the IO action would be like taking a knife to cut the recipe in pieces, and expecting that you can then use that recipe to bake a ready-cut cake. Clearly that's not how it works.

Likewise, you first need to execute (bind) an IO action before you can manipulate the result. That's what happens on the line arr <- replicateM size getLine: the action is executed and only its result stored in arr, so that you can then write map parsePair arr.

Alternatively, you can use the fmap operator. What this does is basically, it takes a recipe and some instruction what to do with the result, and then adds that instruction to the end of the recipe. If you then execute that recipe, the result of it will indeed be a cake cut into pieces.

like image 30
leftaroundabout Avatar answered Jan 14 '23 18:01

leftaroundabout