首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于“虚拟人”的最简单的非平凡单变压器示例,IO+Maybe

用于“虚拟人”的最简单的非平凡单变压器示例,IO+Maybe
EN

Stack Overflow用户
提问于 2015-09-15 06:28:38
回答 3查看 8.5K关注 0票数 55

有人能给出一个超级简单的(几行)单变量转换器的例子吗?这是一个非平凡的例子(例如,不使用我理解的标识单)。

例如,有人将如何创建一个执行IO并能够处理失败(可能)的monad?

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

我已经浏览了几个单台变压器教程,它们似乎都使用了State或解析器或一些复杂的东西(对于New蜜蜂来说)。我想看到比这更简单的东西。我认为IO+Maybe很简单,但我自己也不知道怎么做。

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

在什么样的用例中,人们会想要使用IO+Maybe monad还是Maybe+IO monad?创建这样一个复合单片有意义吗?如果是,什么时候,为什么?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-09-15 09:12:04

这是可用的这里作为一个.lhs文件。

MaybeT转换器将允许我们脱离单一计算,就像抛出异常一样。

我将首先快速地复习一些初步的内容。跳到,为一个有效的示例向IO添加可能的功能。

第一,一些进口:

代码语言:javascript
复制
 import Control.Monad
 import Control.Monad.Trans
 import Control.Monad.Trans.Maybe

经验法则:

在单一堆栈中,IO总是位于底部。

其他类似IO的单元组通常也会出现在底部,例如状态转换器monad ST

MaybeT m是一种新的monad类型,它将可能的monad的功能添加到monad m中,例如MaybeT IO

我们稍后再谈这股力量是什么。现在,习惯于将MaybeT IO看作是maybe+IO单栈。

就像IO Int是返回Int的monad表达式一样,MaybeT IO Int是返回IntMaybeT IO表达式。

习惯阅读复式签名是理解单台变压器的一半。

do块中的每个表达式都必须来自同一个monad。

也就是说,这是因为每个语句都位于IO-monad中:

代码语言:javascript
复制
 greet :: IO ()                               -- type:
 greet = do putStr "What is your name? "      -- IO ()
            n <- getLine                      -- IO String
            putStrLn $ "Hello, " ++ n         -- IO ()

这将无法工作,因为putStr不在MaybeT IO单子中:

代码语言:javascript
复制
mgreet :: MaybeT IO ()
mgreet = do putStr "What is your name? "    -- IO monad - need MaybeT IO here
            ...

幸运的是,有办法解决这个问题。

若要将IO表达式转换为MaybeT IO表达式,请使用liftIO

liftIO是多态的,但在我们的示例中,它具有以下类型:

代码语言:javascript
复制
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函数是:

代码语言:javascript
复制
runMaybeT :: MaybeT IO a -> IO (Maybe a)

示例:

代码语言:javascript
复制
ghci> :t runMaybeT mgreet 
mgreet :: IO (Maybe ())

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

还可以尝试运行:

代码语言:javascript
复制
runMaybeT (forever mgreet)

您需要使用Ctrl来打破循环。

到目前为止,mgreet只做了我们在IO中可以做的事情。现在,我们将研究一个示例,它演示了混合可能的单播和IO的功能。

向IO添加可能的权限

我们将从一个程序开始,问一些问题:

代码语言:javascript
复制
 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来尽早结束调查。我们可以这样处理:

代码语言:javascript
复制
 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单电源变压器来帮助我们。

代码语言:javascript
复制
 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类型的。

我们使用了一个新功能:

代码语言:javascript
复制
MaybeT :: IO (Maybe a) -> MaybeT IO a

下面是这些类型的工作原理:

代码语言:javascript
复制
                  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。

现在我们可以这样写我们的调查函数:

代码语言:javascript
复制
 survey2 :: IO (Maybe (String,String))
 survey2 =
   runMaybeT $ do a <- askfor2 "name"
                  b <- askfor2 "favorite color"
                  return (a,b)

尝试运行survey2并以键入END作为对这两个问题的响应来提前结束问题。

捷径

我知道如果我不提下面的捷径,我会得到别人的评论。

这句话:

代码语言:javascript
复制
MaybeT (return (Just r))    -- return is from the IO monad

也可以简单地写成:

代码语言:javascript
复制
return r                    -- return is from the MaybeT IO monad

另外,编写MaybeT (return Nothing)的另一种方法是:

代码语言:javascript
复制
mzero

此外,两个连续的liftIO语句可能总是合并成一个liftIO,例如:

代码语言:javascript
复制
do liftIO $ statement1
   liftIO $ statement2 

与以下相同:

代码语言:javascript
复制
liftIO $ do statement1
            statement2

通过这些更改,可以编写我们的askfor2函数:

代码语言:javascript
复制
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成为了打破单一模式的一种方式--就像抛出一个异常。

另一个例子

考虑一下这个简单的密码询问循环:

代码语言:javascript
复制
loop1 = do putStr "Password:"
           p <- getLine
           if p == "SECRET"
             then return ()
             else loop1

这是一个(尾)递归函数,工作正常。

在一种传统的语言中,我们可以将它写成一个无限with循环,其中包含一个break语句:

代码语言:javascript
复制
def loop():
    while True:
        p = raw_prompt("Password: ")
        if p == "SECRET":
            break

使用MaybeT,我们可以以与Python代码相同的方式编写循环:

代码语言:javascript
复制
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更容易。

票数 105
EN

Stack Overflow用户

发布于 2015-09-15 07:19:01

假设您必须使用在某种意义上“可能失败”的IO值,例如foo :: IO (Maybe a)func1 :: a -> IO (Maybe b)func2 :: b -> IO (Maybe c)

手动检查绑定链中是否存在错误会很快产生可怕的“厄运楼梯”:

代码语言:javascript
复制
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,这样我们就省去了自己检查它的麻烦。有点像

代码语言:javascript
复制
newtype MaybeOverIO a = MaybeOverIO { runMaybeOverIO :: IO (Maybe a) }

使用bind函数:

代码语言:javascript
复制
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的特定函数。泛化新类型,我们可以使这个工作为任何潜在的单!

代码语言:javascript
复制
newtype MaybeOverM m a = MaybeOverM { runMaybeOverM :: m (Maybe a) }

本质上,这就是MaybeT转换器作品。我忽略了一些细节,比如如何为转换器实现return,以及如何将IO值“提升”到MaybeOverM IO值中。

注意,MaybeOverIO有种类* -> *,而MaybeOverM有种类(* -> *) -> * -> * (因为它的第一个“类型参数”是一个单类型构造函数,它本身需要一个“类型参数”)。

票数 14
EN

Stack Overflow用户

发布于 2015-09-15 06:54:30

当然,MaybeT单台变压器是:

代码语言:javascript
复制
newtype MaybeT m a = MaybeT {unMaybeT :: m (Maybe a)}

我们可以这样实现它的monad实例:

代码语言:javascript
复制
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。

例如,假设我们有这样一个函数:

代码语言:javascript
复制
getDatabaseResult :: String -> IO (Maybe String)

我们可以根据该函数的结果独立地操作monads,但是如果我们按这样的方式组合它:

代码语言:javascript
复制
MaybeT . getDatabaseResult :: String -> MaybeT IO String

我们可以忘记那个额外的一元层,把它当作普通的一层。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32579133

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档