Home > Mobile >  What is the nature of accumulating/iterating recursively and IO operations within a function of type
What is the nature of accumulating/iterating recursively and IO operations within a function of type

Time:05-29

...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 to return type a?
  • As we can see we can perform IO () operations in a function of type IO 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 of f' 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 to return type a?

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 returning a value of type a.

As we can see we can perform IO () operations in a function of type IO 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.

  • Related