我对使用Haskell的IO有点新意,虽然我经常阅读它,但我的代码仍无效。
我希望该应用做什么:
这是我到目前为止的代码。我可以确保函数“middle”在传递[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
我从“Int - &gt; Content”函数调用getSortedMiddleElement(我使用Yesod),其中数字通过URL传递,中间元素应该返回给用户。要从字符串中获取Content,它必须是“String”,而不是“IO String”......如何轻松实现?
提前致谢!
答案 0 :(得分:5)
您的类型签名表示您的函数是纯函数(即,它需要一个Int并返回一个String)但在内部,您正在执行IO! Haskell不会让你写这样的函数。你从文件中读到的任何东西都会永远停留在IO monad中,那就是那个(当然,除非是不安全的函数)。
在这种情况下,事实并非如此糟糕,因为Yesod是一个基于IO的重度框架。所有网络流量也都停留在IO monad中!
当你进入monad变换器堆栈时,你可以在堆栈的每个级别访问monadic计算,但只有一个直接。使用lift
将计算从堆栈中的monad一层向下移动到转换后的monad中。如果IO
位于堆栈中,无论有多少层,您都可以通过liftIO
直接访问其操作。
因此,如果你有type T = ReaderT String IO
,那么你可能有foo :: Int -> T String
函数。在此函数中,您将在T
monad中运行,该monad使用IO
monad功能转换Reader
monad。在这种情况下,您可以说lift readFile
而不是获得IO String
结果,您将获得T String
结果!这只是IO String
包裹在ReaderT
类型中,所以不要认为我们做了任何棘手的事情就像逃离IO
monad一样。这可能有点令人困惑,所以让我们看一个例子:
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
但是,如果我们离IO超过一层呢?我们试一试:
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