...assuming a
is some type e.g. [Int], and we want to iterate e.g. taking all elements, operate on them and print the result after the operations, like operations take place in the following code:
fa :: [Int] -> [Int]
fa [] = []
fa (n:rln) = (n 1) : fa rln
...but with in a function of a type like
fb :: [Int] -> IO [Int]
...such that
main :: IO ()
main =
do
fb [3, 2, 6, 8] >>= print
...would print the given list in order ([4, 3, 7, 9]) and again print the result.
Differentiation and Background
I would like to understand the nature of type 'IO a'.
This question is not about how to iterate with ready to use functions like folds and fmap.
This is not a question about whether it makes sense to print and return the same result.
Code so far
So far I have got this "beast":
f :: [Int] -> IO [Int]
f ln = f' ln >>= print >>= return (f' ln)
where
f' :: [Int] -> IO [Int]
f' [] = return []
f' (n:rln) = f' rln >>= (\r -> return ((n 1) : r))
...which can be applied in main like this
main :: IO ()
main = f [3, 2, 6, 8] >>= (putStrLn . (\r -> "result=" show r))
...prints:
[4,3,7,9]
result=[4,3,7,9]
For me, suspicious is the line f ln = f' ln >>= print >>= return (f' ln)
that does not pass the result of f' ln
to return.
Question(s)
What is the nature of type IO a
?
In particular:
- Is it correct to say that a function of type
IO a
always has toreturn
typea
? - As we can see we can perform
IO ()
operations in a function of typeIO a
- but do we really have to have such a clumsy code if we print in such functions? - What would be the most effective implementation?
- Should we generally avoid the dual use (IO operations and recursive computations) in one function?
CodePudding user response:
For me, suspicious is the line
f ln = f' ln >>= print >>= return (f' ln)
that does not pass the result off' ln
to return.
I agree, that seems clearly wrong. Perhaps you want this instead:
f ln = f' ln >>= \x -> print x >> return x
There are various other ways to say the same thing.
Is it correct to say that a function of type
IO a
always has toreturn
typea
?
No, for a couple reasons. First: a value of type IO a
is not a function! Second: it's not the case that an IO a
action always returns; for example exitWith ExitSuccess
doesn't. Finally, and most critically: return
is not the only way to end an IO
action, as there are many other base actions. For example, getChar
is an IO
action and does not call return
-- it is implemented as a primitive by the compiler.* An action of type IO a
is always allowed to finalize itself by calling some other action of type IO a
to finish, including compiler primitives, instead of return
ing a value of type a
.
As we can see we can perform
IO ()
operations in a function of typeIO a
- but do we really have to have such a clumsy code if we print in such functions?
I don't know. I don't know what you consider clumsy. If you can make this question more objective, I'd be happy to take a stab at answering it.
What would be the most effective implementation?
I would write main = mapM foo [3, 2, 6, 8] >>= print
. If you don't allow me to use mapM
from the standard library, I would implement it first, e.g. as
mapM f [] = return []
mapM f (x:xs) = do
y <- f x
ys <- mapM f xs
return (y:ys)
Should we generally avoid the dual use (IO operations and recursive computations) in one function?
No, IO and recursive computations love each other; almost every main
I've ever written mixed IO
and recursion.
* Okay, I don't actually know whether getChar
calls return
. It might. But if it does, it is because there is some other, more primitive thing that it calls which does not end with a return
.