jhe*_*dus 39 monads haskell monad-transformers
有人可以提供一个超级简单(几行)monad变换器示例,这是非平凡的(即不使用Identity monad - 我理解).
例如,有人会如何创建一个执行IO并可以处理失败的monad(可能)?
什么是最简单的例子来证明这一点?
我已经浏览了一些monad变换器教程,他们似乎都使用State Monad或Parsers或者复杂的东西(对于newbee).我想看到一些比这简单的东西.我认为IO +也许会很简单,但我自己并不知道如何做到这一点.
我怎么能使用IO + Maybe monad堆栈?最重要的是什么?什么会在底部?为什么?
在什么样的用例中,人们想要使用IO + Maybe monad还是Maybe + IO monad?创造这样一个复合单子会有意义吗?如果是,何时以及为什么?
Eri*_*ikR 81
这可以在 .lhs文件中找到.
该MaybeT变压器将使我们能够打出来单子计算就像抛出异常.
我会先快点一些预赛.跳过了添加也许权力IO的工作实例.
首先进口一些:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
Run Code Online (Sandbox Code Playgroud)
经验法则:
在monad堆栈中,IO始终位于底部.
其他类似IO的monad通常也会出现在底部,例如状态变换器monad ST.
MaybeT m是一种新的monad类型,它将Maybe monad的强大功能添加到monad中m- 例如MaybeT IO.
我们将进入后期的力量.现在,习惯于将其MaybeT IO视为可能+ IO monad堆栈.
就像
IO Int一个monad表达式返回一个Int,MaybeT IO Int是一个MaybeT IO表达式返回一个Int.
习惯阅读复合型签名是了解monad变形金刚的一半.
do块中的每个表达式都必须来自同一个monad.
即这是有效的,因为每个语句都在IO-monad中:
greet :: IO () -- type:
greet = do putStr "What is your name? " -- IO ()
n <- getLine -- IO String
putStrLn $ "Hello, " ++ n -- IO ()
Run Code Online (Sandbox Code Playgroud)
这不起作用,因为putStr不在MaybeT IOmonad中:
mgreet :: MaybeT IO ()
mgreet = do putStr "What is your name? " -- IO monad - need MaybeT IO here
...
Run Code Online (Sandbox Code Playgroud)
幸运的是,有一种方法可以解决这个问题.
将
IO表达式转换为MaybeT IO表达式使用liftIO.
liftIO 是多态的,但在我们的例子中它有类型:
liftIO :: IO a -> MaybeT IO a
mgreet :: MaybeT IO () -- types:
mgreet = do liftIO $ putStr "What is your name? " -- MaybeT IO ()
n <- liftIO getLine -- MaybeT IO String
liftIO $ putStrLn $ "Hello, " ++ n -- MaybeT IO ()
Run Code Online (Sandbox Code Playgroud)
现在所有的声明mgreet都来自MaybeT IOmonad.
每个monad变换器都具有"运行"功能.
run函数"运行"monad堆栈的最顶层,从内层返回一个值.
对于MaybeT IO,运行功能是:
runMaybeT :: MaybeT IO a -> IO (Maybe a)
Run Code Online (Sandbox Code Playgroud)
例:
ghci> :t runMaybeT mgreet
mgreet :: IO (Maybe ())
ghci> runMaybeT mgreet
What is your name? user5402
Hello, user5402
Just ()
Run Code Online (Sandbox Code Playgroud)
也尝试运行:
runMaybeT (forever mgreet)
Run Code Online (Sandbox Code Playgroud)
你需要使用Ctrl-C来摆脱循环.
到目前为止mgreet,除了我们在IO中可以做的事情之外,什么都没做.现在我们将研究一个示例,该示例演示了将Maybe monad与IO混合的强大功能.
我们将从一个提出一些问题的程序开始:
askfor :: String -> IO String
askfor prompt = do
putStr $ "What is your " ++ prompt ++ "? "
getLine
survey :: IO (String,String)
survey = do n <- askfor "name"
c <- askfor "favorite color"
return (n,c)
Run Code Online (Sandbox Code Playgroud)
现在假设我们希望通过在回答问题时输入END来让用户能够提前结束调查.我们可以这样处理它:
askfor1 :: String -> IO (Maybe String)
askfor1 prompt = do
putStr $ "What is your " ++ prompt ++ " (type END to quit)? "
r <- getLine
if r == "END"
then return Nothing
else return (Just r)
survey1 :: IO (Maybe (String, String))
survey1 = do
ma <- askfor1 "name"
case ma of
Nothing -> return Nothing
Just n -> do mc <- askfor1 "favorite color"
case mc of
Nothing -> return Nothing
Just c -> return (Just (n,c))
Run Code Online (Sandbox Code Playgroud)
问题是,survey1如果我们添加更多问题,就会出现熟悉的阶梯问题.
我们可以使用MaybeT monad变换器来帮助我们.
askfor2 :: String -> MaybeT IO String
askfor2 prompt = do
liftIO $ putStr $ "What is your " ++ prompt ++ " (type END to quit)? "
r <- liftIO getLine
if r == "END"
then MaybeT (return Nothing) -- has type: MaybeT IO String
else MaybeT (return (Just r)) -- has type: MaybeT IO String
Run Code Online (Sandbox Code Playgroud)
请注意所有状态都askfor2具有相同的monad类型.
我们使用了一个新功能:
MaybeT :: IO (Maybe a) -> MaybeT IO a
Run Code Online (Sandbox Code Playgroud)
以下是这些类型的解决方法:
Nothing :: Maybe String
return Nothing :: IO (Maybe String)
MaybeT (return Nothing) :: MaybeT IO String
Just "foo" :: Maybe String
return (Just "foo") :: IO (Maybe String)
MaybeT (return (Just "foo")) :: MaybeT IO String
Run Code Online (Sandbox Code Playgroud)
这return是来自IO-monad.
现在我们可以像这样编写我们的调查函数:
survey2 :: IO (Maybe (String,String))
survey2 =
runMaybeT $ do a <- askfor2 "name"
b <- askfor2 "favorite color"
return (a,b)
Run Code Online (Sandbox Code Playgroud)
尝试运行survey2并提前结束问题,输入END作为对任一问题的回答.
我知道如果我不提及以下捷径,我会得到人们的评论.
表达方式:
MaybeT (return (Just r)) -- return is from the IO monad
Run Code Online (Sandbox Code Playgroud)
也可以简单地写成:
return r -- return is from the MaybeT IO monad
Run Code Online (Sandbox Code Playgroud)
另外,另一种写作方式MaybeT (return Nothing)是:
mzero
Run Code Online (Sandbox Code Playgroud)
此外,两个连续的liftIO语句可能总是组合成一个liftIO,例如:
do liftIO $ statement1
liftIO $ statement2
Run Code Online (Sandbox Code Playgroud)
是相同的:
liftIO $ do statement1
statement2
Run Code Online (Sandbox Code Playgroud)
通过这些更改,我们的askfor2功能可以写成:
askfor2 prompt = do
r <- liftIO $ do
putStr $ "What is your " ++ prompt ++ " (type END to quit)?"
getLine
if r == "END"
then mzero -- break out of the monad
else return r -- continue, returning r
Run Code Online (Sandbox Code Playgroud)
从某种意义上说,mzero成为一种打破monad的方式 - 就像抛出异常一样.
考虑这个简单的密码问循环:
loop1 = do putStr "Password:"
p <- getLine
if p == "SECRET"
then return ()
else loop1
Run Code Online (Sandbox Code Playgroud)
这是一个(尾部)递归函数,工作得很好.
在传统语言中,我们可以将其写为带有break语句的无限while循环:
def loop():
while True:
p = raw_prompt("Password: ")
if p == "SECRET":
break
Run Code Online (Sandbox Code Playgroud)
使用MaybeT,我们可以像Python代码一样编写循环:
loop2 :: IO (Maybe ())
loop2 = runMaybeT $
forever $
do liftIO $ putStr "Password: "
p <- liftIO $ getLine
if p == "SECRET"
then mzero -- break out of the loop
else return ()
Run Code Online (Sandbox Code Playgroud)
最后一次return ()继续执行,因为我们处于forever循环中,所以控制权传递回do块的顶部.请注意,唯一loop2可以返回的值是Nothing对应于断开循环.
根据情况,您可能会发现编写更容易,loop2而不是递归loop1.
dan*_*iaz 13
假设你有工作IO的值是"可能会失败,"在某种意义上,像 foo :: IO (Maybe a),func1 :: a -> IO (Maybe b)和func2 :: b -> IO (Maybe c).
手动检查一系列绑定中是否存在错误会产生可怕的"厄运的阶梯":
do
ma <- foo
case ma of
Nothing -> return Nothing
Just a -> do
mb <- func1 a
case mb of
Nothing -> return Nothing
Just b -> func2 b
Run Code Online (Sandbox Code Playgroud)
如何以某种方式"自动化"这个?也许我们可以IO (Maybe a)使用绑定函数设计一个newtype ,它会自动检查第一个参数是否为Nothing内部IO,这样就省去了自己检查它的麻烦.就像是
newtype MaybeOverIO a = MaybeOverIO { runMaybeOverIO :: IO (Maybe a) }
Run Code Online (Sandbox Code Playgroud)
使用绑定功能:
betterBind :: MaybeOverIO a -> (a -> MaybeOverIO b) -> MaybeOverIO b
betterBind mia mf = MaybeOverIO $ do
ma <- runMaybeOverIO mia
case ma of
Nothing -> return Nothing
Just a -> runMaybeOverIO (mf a)
Run Code Online (Sandbox Code Playgroud)
这有效!而且,仔细观察它,我们意识到我们没有使用IOmonad 独有的任何特定功能.稍微概括一下newtype,我们可以为任何潜在的monad做这个工作!
newtype MaybeOverM m a = MaybeOverM { runMaybeOverM :: m (Maybe a) }
Run Code Online (Sandbox Code Playgroud)
从本质上讲,这就是MaybeT变压器的方式工作原理.我遗漏了一些细节,比如如何实现return变压器,以及如何将IO值"提升" 到MaybeOverM IO值中.
请注意 MaybeOverIO有一种* -> *同时MaybeOverM具有那种(* -> *) -> * -> *(因为它的第一个"类型参数"是一个单子类型构造,本身需要一个"类型参数").
当然,MaybeTmonad变压器是:
newtype MaybeT m a = MaybeT {unMaybeT :: m (Maybe a)}
Run Code Online (Sandbox Code Playgroud)
我们可以这样实现它的monad实例:
instance (Monad m) => Monad (MaybeT m) where
return a = MaybeT (return (Just a))
(MaybeT mmv) >>= f = MaybeT $ do
mv <- mmv
case mv of
Nothing -> return Nothing
Just a -> unMaybeT (f a)
Run Code Online (Sandbox Code Playgroud)
这将允许我们执行IO,在某些情况下可以选择优雅地失败.
例如,假设我们有这样的函数:
getDatabaseResult :: String -> IO (Maybe String)
Run Code Online (Sandbox Code Playgroud)
我们可以使用该函数的结果独立地操作monad,但是如果我们这样组成它:
MaybeT . getDatabaseResult :: String -> MaybeT IO String
Run Code Online (Sandbox Code Playgroud)
我们可以忘记那个额外的monadic层,并将它视为一个普通的monad.