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
Because the type of replicateM size getLine
is IO [String]
, it is not a list of String
s, 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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With