Reputation: 3219
I am kind of new to IO with Haskell and although I read through it a lot, my code still won't work.
What I want the app to do:
This is the code I do have so far. I can assure that the function "middle" works fine when passing a [String].
middle :: [a] -> a
middle xs = (drop ((l - 1) `div ` 2) xs) !! 0
where l = length xs
getSortedMiddleElement :: Int -> String
getSortedMiddleElement i = do
dat <- readFile $ "file" ++ (show i) ++ ".txt"
return $ middle $ sort $ lines dat
I am calling getSortedMiddleElement from a "Int -> Content" function (I use Yesod), where the number is being passed via URL and the middle element should be returned to the user. To get Content out of a string, it needs to be "String", not "IO String"... How can this be easily achieved?
Thanks in advance!
Upvotes: 0
Views: 337
Reputation: 4984
Your type signature says that your function is pure (i.e., it takes an Int and returns a String) but inside, you are performing IO! Haskell will not let you write such a function. Anything you read from a file is forever stuck in the IO monad, and that's that (barring unsafe functions, of course).
In this case, that turns out to not be so bad, because Yesod is a heavily IO-based framework. All network traffic is stuck in the IO monad as well!
When you're in a monad transformer stack, you have access to monadic computations at each level of the stack, but only one of them directly. You use lift
to move a computation from a monad one layer down in the stack into the transformed monad. If IO
is in the stack, no matter how many layers down, you can access its actions directly via liftIO
.
So if you have type T = ReaderT String IO
then you may have a function foo :: Int -> T String
. In this function, you'll be operating in the T
monad, which transforms the IO
monad with the Reader
monad capabilities. In this context, you can say lift readFile
and instead of getting an IO String
result, you'll get a T String
result! That's just an IO String
wrapped in the ReaderT
type, though, so don't think we did anything tricky like escaping the IO
monad. That might have been a bit confusing, so let's look at an example:
import Control.Monad.Reader (ReaderT)
import Control.Monad.Writer (WriterT)
import Control.Monad.Trans (lift, liftIO)
type T = ReaderT String IO
getSortedMiddleElement :: Int -> IO String
foo :: Int -> T String
foo n = do
str <- lift $ getSortedMiddleElement n --str holds a pure String now
lift $ putStrLn str --get `putStrLn` from IO and pass the String
return str --let's wrap it back in T now
But what if we're more than one layer away from IO? Let's try it out:
type W = WriterT String T -- WriterT String (ReaderT String IO)
-- This doesn't work; lift only gives you access to the next layer's actions
-- but IO is now more than one layer away!
--
--bar n = do
-- str <- lift $ getSortedMiddleElement n
-- Instead, we need liftIO, which will access IO across many transformer layers
bar :: Int -> W String
bar n = do
str <- liftIO $ getSortedMiddleElement n
liftIO $ putStrLn str
return str
Upvotes: 5