有人可以给出一个超级简单(几行)monad变换器示例,这是非平凡的(即不使用Identity monad - 我理解)。
例如,有人会如何创建一个执行IO并且可以处理失败的monad(可能)?
最简单的例子是什么??
我已经浏览了一些monad变换器教程,他们似乎都使用State Monad或Parsers或者复杂的东西(对于newbee)。我想看到一些比这简单的东西。我认为IO +也许很简单,但我自己也不知道如何做到这一点。
我怎么能使用IO + Maybe monad堆栈? 最重要的是什么?什么会在底部?为什么呢?
在什么样的用例中,人们想要使用IO + Maybe monad还是Maybe + IO monad?创造这样一个复合单子会有意义吗?如果是,何时以及为什么?
答案 0 :(得分:72)
这是{。3}}可用的.lhs文件。
MaybeT
转换器将允许我们打破monad计算,就像抛出异常一样。
我先快点过一些预赛。对于一个有效的例子,请跳到将可能的权力添加到IO 。
首先进口一些:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
经验法则:
在monad堆栈中IO始终位于底部。
其他类似IO的monad也通常会出现在底部,例如:国家变压器monad ST
。
MaybeT m
是一种新的monad类型,它将Maybe monad的强大功能添加到monadm
- 例如MaybeT IO
。
我们将进入后来的力量。现在,习惯于将MaybeT IO
视为可能+ IO monad堆栈。
就像
IO Int
是一个返回Int
的monad表达式一样,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 ()
这不起作用,因为putStr
不在MaybeT IO
monad:
mgreet :: MaybeT IO ()
mgreet = do putStr "What is your name? " -- IO monad - need MaybeT IO here
...
幸运的是,有办法解决这个问题。
要将
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 ()
现在mgreet
中的所有声明都来自MaybeT IO
monad。
每个monad变压器都有一个&#34; run&#34;功能
运行功能&#34;运行&#34;返回的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。
我们将从一个提出问题的程序开始:
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块的顶部。请注意,loop2
可以返回的唯一值是Nothing
,这对应于突破循环。
根据具体情况,您可能会更容易编写loop2
而不是递归loop1
。
答案 1 :(得分:13)
假设您必须使用IO
值,这些值可能会失败&#34;在某种意义上,例如foo :: IO (Maybe a)
,func1 :: a -> IO (Maybe b)
和func2 :: b -> IO (Maybe c)
。
手动检查一系列绑定中是否存在错误会产生可怕的厄运楼梯&#34;:
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
如何&#34;自动化&#34;这在某种程度上?也许我们可以使用绑定函数在IO (Maybe a)
周围设计一个新类型,该函数会自动检查Nothing
内的第一个参数是否为IO
,从而避免了我们自己检查它的麻烦。像
newtype MaybeOverIO a = MaybeOverIO { runMaybeOverIO :: IO (Maybe a) }
使用绑定功能:
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)
这个有效!而且,仔细观察它,我们意识到我们没有使用IO
monad独有的任何特定函数。稍微概括一下newtype,我们可以为任何潜在的monad做这个工作!
newtype MaybeOverM m a = MaybeOverM { runMaybeOverM :: m (Maybe a) }
从本质上讲,这是MaybeT
变形works的方式。我遗漏了一些细节,比如如何为变压器实施return
,以及如何提升&#34; IO
值为MaybeOverM IO
个值。
请注意,MaybeOverIO
有* -> *
种,而MaybeOverM
有(* -> *) -> * -> *
种类(因为它的第一个&#34;类型参数&#34;是monad类型的构造函数,本身需要一个&#34;类型的参数&#34;)。
答案 2 :(得分:7)
当然,MaybeT
monad变换器是:
newtype MaybeT m a = MaybeT {unMaybeT :: m (Maybe a)}
我们可以这样实现它的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)
这将允许我们在某些情况下执行优先级失败的选项。
例如,假设我们有这样的函数:
getDatabaseResult :: String -> IO (Maybe String)
我们可以使用该函数的结果独立地操作monad,但是如果我们这样组成它:
MaybeT . getDatabaseResult :: String -> MaybeT IO String
我们可以忘记那个额外的monadic图层,并将它视为普通的monad。