有人能给出一个超级简单的(几行)单变量转换器的例子吗?这是一个非平凡的例子(例如,不使用我理解的标识单)。
例如,有人将如何创建一个执行IO并能够处理失败(可能)的monad?
最简单的例子是什么来证明这一点呢?
我已经浏览了几个单台变压器教程,它们似乎都使用了State或解析器或一些复杂的东西(对于New蜜蜂来说)。我想看到比这更简单的东西。我认为IO+Maybe很简单,但我自己也不知道怎么做。
我如何使用IO+Maybe单栈?上面会是什么?底部会是什么?为什么?
在什么样的用例中,人们会想要使用IO+Maybe monad还是Maybe+IO monad?创建这样一个复合单片有意义吗?如果是,什么时候,为什么?
发布于 2015-09-15 09:12:04
这是可用的这里作为一个.lhs文件。
MaybeT转换器将允许我们脱离单一计算,就像抛出异常一样。
我将首先快速地复习一些初步的内容。跳到,为一个有效的示例向IO添加可能的功能。
第一,一些进口:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe经验法则:
在单一堆栈中,IO总是位于底部。
其他类似IO的单元组通常也会出现在底部,例如状态转换器monad ST。
MaybeT m是一种新的monad类型,它将可能的monad的功能添加到monadm中,例如MaybeT IO。
我们稍后再谈这股力量是什么。现在,习惯于将MaybeT IO看作是maybe+IO单栈。
就像
IO Int是返回Int的monad表达式一样,MaybeT IO Int是返回Int的MaybeT IO表达式。
习惯阅读复式签名是理解单台变压器的一半。
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单子中:
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。
每个单台变压器都有一个“运行”功能。
run函数“运行”monad堆栈的最顶层,从内部层返回值。
对于MaybeT IO,run函数是:
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来打破循环。
到目前为止,mgreet只做了我们在IO中可以做的事情。现在,我们将研究一个示例,它演示了混合可能的单播和IO的功能。
向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)现在,假设我们希望让用户能够通过在回答一个问题时输入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单电源变压器来帮助我们。
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成为了打破单一模式的一种方式--就像抛出一个异常。
另一个例子
考虑一下这个简单的密码询问循环:
loop1 = do putStr "Password:"
p <- getLine
if p == "SECRET"
then return ()
else loop1这是一个(尾)递归函数,工作正常。
在一种传统的语言中,我们可以将它写成一个无限with循环,其中包含一个break语句:
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更容易。
发布于 2015-09-15 07:19:01
假设您必须使用在某种意义上“可能失败”的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如何在某种程度上“自动化”这一点?也许我们可以在IO (Maybe a)周围设计一个新类型,使用一个绑定函数自动检查第一个参数是否是IO中的Nothing,这样我们就省去了自己检查它的麻烦。有点像
newtype MaybeOverIO a = MaybeOverIO { runMaybeOverIO :: IO (Maybe a) }使用bind函数:
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 MaybeOverM m a = MaybeOverM { runMaybeOverM :: m (Maybe a) }本质上,这就是MaybeT转换器作品。我忽略了一些细节,比如如何为转换器实现return,以及如何将IO值“提升”到MaybeOverM IO值中。
注意,MaybeOverIO有种类* -> *,而MaybeOverM有种类(* -> *) -> * -> * (因为它的第一个“类型参数”是一个单类型构造函数,它本身需要一个“类型参数”)。
发布于 2015-09-15 06:54:30
当然,MaybeT单台变压器是:
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)这将允许我们在某些情况下选择优雅地失败来执行IO。
例如,假设我们有这样一个函数:
getDatabaseResult :: String -> IO (Maybe String)我们可以根据该函数的结果独立地操作monads,但是如果我们按这样的方式组合它:
MaybeT . getDatabaseResult :: String -> MaybeT IO String我们可以忘记那个额外的一元层,把它当作普通的一层。
https://stackoverflow.com/questions/32579133
复制相似问题