gpt4 book ai didi

haskell - "dummies", IO+Maybe 的最简单的非平凡 monad 转换器示例

转载 作者:行者123 更新时间:2023-12-02 11:30:34 25 4
gpt4 key购买 nike

有人可以给出一个 super 简单(几行)的单子(monad)转换器示例,这是不平凡的(即不使用 Identity monad - 我理解)。

例如,有人将如何创建一个执行 IO 并可以处理故障的 monad(也许)?

能证明这一点的最简单的例子是什么?

我浏览了一些 monad 转换器教程,它们似乎都使用 State Monad 或 Parsers 或一些复杂的东西(对于新手来说)。我希望看到比这更简单的东西。我认为 IO+Maybe 会很简单,但我自己真的不知道该怎么做。

我如何使用 IO+Maybe monad 堆栈?
什么会在上面?底部会是什么?为什么?

在哪种用例中需要使用 IO+Maybe monad 或 Maybe+IO monad?创建这样一个复合单子(monad)是否有意义?如果是,何时以及为什么?

最佳答案

这是可用的here作为 .lhs 文件。
MaybeT Transformer 将允许我们像抛出异常一样打破单子(monad)计算。

我将首先快速回顾一些预备知识。跳至 为 IO 添加 Maybe 权限 对于一个工作的例子。

首先是一些进口:

 import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe

经验法则:

In a monad stack IO is always on the bottom.



通常,其他类似 IO 的 monad 也将始终出现在底部,例如状态变压器单子(monad) ST .

MaybeT m is a new monad type which adds the power of the Maybe monad to the monad m - e.g. MaybeT IO.



我们稍后会讨论这种力量。现在,习惯于思考 MaybeT IO作为可能+IO monad 堆栈。

Just like IO Int is a monad expression returning an Int, MaybeT IO Int is a MaybeT IO expression returning an Int.



习惯阅读复合类型签名是理解 monad 转换器的一半。

Every expression in a do block must be from the same monad.



IE。这是有效的,因为每个语句都在 IO-monad 中:
 greet :: IO ()                               -- type:
greet = do putStr "What is your name? " -- IO ()
n <- getLine -- IO String
putStrLn $ "Hello, " ++ n -- IO ()

这将不起作用,因为 putStr不在 MaybeT IO 中单子(monad):
mgreet :: MaybeT IO ()
mgreet = do putStr "What is your name? " -- IO monad - need MaybeT IO here
...

幸运的是,有一种方法可以解决这个问题。

To transform an IO expression into a MaybeT IO expression use 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 ()

现在 mgreet 中的所有语句来自 MaybeT IO单子(monad)。

Every monad transformer has a "run" function.



run 函数“运行”返回的 monad 堆栈的最顶层
来自内层的值。

对于 MaybeT IO ,运行函数为:
runMaybeT :: MaybeT IO a -> IO (Maybe a)

例子:
ghci> :t runMaybeT mgreet 
mgreet :: IO (Maybe ())

ghci> runMaybeT mgreet
What is your name? user5402
Hello, user5402
Just ()

也尝试运行:
runMaybeT (forever mgreet)

您需要使用 Ctrl-C 来跳出循环。

到目前为止 mgreet除了我们在 IO 中可以做的事情之外,它没有做更多的事情。
现在我们将通过一个例子来展示混合的力量
带有 IO 的 Maybe monad。

为 IO 添加 Maybe 权限

我们将从一个询问一些问题的程序开始:
 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)

现在假设我们要让用户能够结束调查
在回答问题时键入 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))

问题是 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

注意 askfor2 中的所有声明具有相同的单子(monad)类型。

我们使用了一个新功能:
MaybeT :: IO (Maybe a) -> MaybeT IO a

以下是这些类型的工作方式:
                  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

这里 return来自 IO-monad。

现在我们可以像这样编写我们的调查函数:
 survey2 :: IO (Maybe (String,String))
survey2 =
runMaybeT $ do a <- askfor2 "name"
b <- askfor2 "favorite color"
return (a,b)

尝试运行 survey2并通过键入 END 作为对任一问题的回答来提前结束问题。

捷径

我知道如果我不提及以下捷径,我会得到人们的评论。

表达方式:
MaybeT (return (Just r))    -- return is from the IO monad

也可以简单地写成:
return r                    -- return is from the MaybeT IO monad

另外,另一种写法 MaybeT (return Nothing)是:
mzero

此外,两个连续的 liftIO语句总是可以组合成一个 liftIO ,例如:
do liftIO $ statement1
liftIO $ statement2

是相同的:
liftIO $ do statement1
statement2

通过这些更改,我们的 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

从某种意义上说, mzero成为打破单子(monad)的一种方式 - 就像抛出异常一样。

另一个例子

考虑这个简单的密码询问循环:
loop1 = do putStr "Password:"
p <- getLine
if p == "SECRET"
then return ()
else loop1

这是一个(尾)递归函数,工作得很好。

在传统语言中,我们可以将其写为带有 break 语句的无限 while 循环:
def loop():
while True:
p = raw_prompt("Password: ")
if p == "SECRET":
break

使用 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 ()

最后 return ()继续执行,因为我们在 forever循环,控制传递回do block 的顶部。请注意, loop2 的唯一值可以返回是 Nothing这对应于跳出循环。

根据具体情况,您可能会发现 loop2 更容易编写。而不是递归的 loop1 .

关于haskell - "dummies", IO+Maybe 的最简单的非平凡 monad 转换器示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32579133/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com