Search code examples
haskellio-monad

How to turn IO Actions into a "pure" function


Haskell newbie here. I have a higher order function myTransform at hand, which takes a function fn :: String -> String and do some fancy things. Let's assume the implementation is

myTransform :: Int -> (String -> String) -> String -> [String]
myTransform n f = take n . iterate f

now I want to transform an external program, which is, if I understand right, an IO action. Preferably, the signature should be String -> IO String:

import System.Process
externProg :: String -> IO String
externProg s = readProcess "echo" ["-n", s, "+ 1"] ""

the question is, is there any way I can fit this String -> IO String function into that String -> String argument slot, without changing, or even knowing, how myTransform implements?


Solution

  • No, you can not. You will have to make a monadic version of myTransform. It is custom to append a capital M. I.e. map becomes mapM. fold becomse foldM ... Unfortunately there is no iterateM. I would therefore skip iterateM and implement it directly.

    myTransformM' :: (Monad m) => Int -> (String -> m String) -> String -> m [String]
    myTransformM' 0 f str = return [str]
    myTransformM' n f str = do
        results <- myTransformM (n-1) f str
        next <- f (head results)
        return (next:results)
    
    myTransformM n f str = do
        results <- myTransformM' n f str
        return $ reverse results
    

    You might notice that the results of the first function are ordered the other way around. This is in order to avoid the function being quadratic.

    You can try yourself what will happen if you implement iterateM. It will just loop eternally. This is because Haskell can never know if you will actually get a list back or if there will be an IOError somewhere down the road. Similarly if you take the Maybe monad, Haskell will never know if you actually get a Just list back or if down the road somewhere there is a Nothing.

    iterateM :: (Monad m) => (a -> m a) -> a -> m [a]
    iterateM f a = do
        result <- f a
        results <- iterateM f result
        return (result:results)