用于“傻瓜”的最简单的非平凡monad变换器示例,IO + Maybe [英] Simplest non-trivial monad transformer example for "dummies", IO+Maybe

查看:112
本文介绍了用于“傻瓜”的最简单的非平凡monad变换器示例,IO + Maybe的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人可以给出一个超级简单(几行)monad转换器的例子,这是非平凡的(即不使用身份monad - 据我了解)。

例如,某人如何创建一个能够处理IO并且可以处理失败(可能)的monad?



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

我已经浏览了几个monad变换器教程并且他们似乎都使用State Monad或Parsers或者一些复杂的(用于新手)。我希望看到比这更简单的事情。我认为IO +也许会很简单,但我真的不知道该怎么做。



我怎样才能使用IO + Maybe monad堆栈?
会是什么?底部会是什么?为什么?



在什么样的用例下,人们想要使用IO + Maybe monad还是Maybe + IO monad?完全可以创建这样的复合monad吗?如果是的话,何时以及为什么?

解决方案

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



MaybeT 变压器将允许我们突破的monad计算,就像抛出一个异常一样。



我会先快速回顾一些预备。跳到下面的例子为IO添加可能的权力



第一部分导入:

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

$ b

经验法则:


在monad堆栈中,IO是总是在底部。


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


MaybeT m 是一个新的monad类型,它将Maybe monad的功能添加到monad m - 例如 MaybeT IO


我们将在稍后讨论该功能。现在,习惯于把 MaybeT IO 当作maybe + IO monad堆栈。


就像 IO int 是返回 Int 的单子表达式, MaybeT IO Int 是一个 MaybeT IO 表达式,返回 Int


习惯阅读复合类型签名是了解monad变形金刚的一半。


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


即这是可行的,因为每个语句都在IO-monad中:
$ b $ pre $ greet :: IO() - 类型:
greet = do putStr你叫什么名字? - IO()
n< - getLine - IO字符串
putStrLn $Hello,++ n - IO()

这不起作用,因为 putStr 不在 MaybeT IO monad:

  mgreet :: MaybeT IO()
mgreet = do putStr你叫什么名字? - IO monad - 需要MaybeT IO在这里
...

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


转换 IO 表达式转换为 MaybeT IO 表达式使用 liftIO




liftIO 是多态的,但在我们的例子中它的类型为:

  liftIO :: IO a  - > MaybeT IO a 

mgreet :: MaybeT IO() - 类型:
mgreet =做liftIO $ putStr你叫什么名字? - MaybeT IO()
n< ; - liftIO getLine - MaybeT IO字符串
liftIO $ putStrLn $Hello,++ n - MaybeT IO()

现在,所有 mgreet 中的语句都来自 MaybeT IO monad。


每个monad转换器都有一个run函数。

运行函数运行monad堆栈的最顶层,从内层返回
a值。



对于 MaybeT IO ,运行函数为:

  runMaybeT :: MaybeT IO a  - > IO(也许a)

示例:

  ghci的> :t runMaybeT mgreet 
mgreet :: IO(Maybe())

ghci> runMaybeT mgreet
你叫什么名字? user5402
您好,user5402
Just()

另请尝试运行:

  runMaybeT(永久mgreet)

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



到目前为止 mgreet 没有做什么比我们在IO中可以做的更多。
现在我们将演示一个示例,演示将
Maybe mon单元与IO混合的功能。



向IO添加Maybe功能 h1>

我们将从一个程序开始提问: :: String - > IO字符串
askfor prompt =做
putStr $什么是你的++提示++?
getLine

survey :: IO(String,String )
survey = do n< - askforname
c< - askforfavorite color
return(n,c)

现在假设我们希望通过在回答问题时输入END来让用户能够提早结束调查
。我们可以这样处理


  askfor1 :: String  - > IO(Maybe String)
askfor1 prompt = do
putStr $什么是你的++ ++ ++(类型END退出)?
r< - getLine
如果r ==END
则返回Nothing
else return(只要r)

survey1 :: IO(Maybe(String,String))
survey1 =
ma< - askfor1name
$ ma $ $ b $无 - > return Nothing
Just n - > do mc< - askfor1最喜欢的颜色
case mc of
Nothing - > return Nothing
只需c - >返回(Just(n,c))

问题在于 survey1 有个熟悉的阶梯问题,如果我们添加更多的问题,
不会扩展。



我们可以使用MaybeT monad变换器来帮助我们这里。

  askfor2 :: String  - > MaybeT IO字符串
askfor2提示符=做
liftIO $ putStr $什么是您的++提示++(类型END退出)?
r< - liftIO getLine
if r ==END
then MaybeT(返回Nothing) - 类型:MaybeT IO字符串
else MaybeT(return(Just r)) - 类型:MaybeT IO字符串

请注意 askfor2 中的所有statemens具有相同monad类型。



我们使用了一个新函数:

pre $ MaybeT :: IO(也许a) - > MaybeT IO a

以下是这些类型的工作方式:

  Nothing :: Maybe String 
return Nothing :: IO(Maybe String)
MaybeT(返回Nothing):: MaybeT IO String

只是foo:: Maybe String
return(Justfoo):: IO(Maybe String)
MaybeT(return(Justfoo)):: MaybeT IO String

这里 return 来自IO-monad。



现在我们可以编写我们的调查函数,如下所示:

  survey2 :: IO(Maybe(String,String))
survey2 =
runMaybeT $ do a< - askfor2name
b< - askfor2favorite color
return( a,b)

尝试运行 survey2



捷径



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



表达式:

  MaybeT(return(Just r)) -  return is from IO monad 

也可以简写为:

  return r  - 返回来自MaybeT IO monad 

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

  mzero 

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

 做liftIO $ statement1 
liftIO $ statement2



  liftIO $ do语句1 
语句2

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

  askfor2提示= do 
r < - liftIO $ do
putStr $什么是你的++提示++(键入END退出)?
getLine
if r ==END
然后mzero - 分出单子
else返回r - 继续,返回r

从某种意义上讲, mzero 成为一种突破monad的方式 - 像抛出一个异常。



另一个例子



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

  loop1 =执行putStr密码:
p < - getLine
if p ==SECRET
然后返回()
else loop1

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



在传统语言中,我们可以将它写成一个带有break语句的无限循环:

  def loop():
而真:
p = raw_prompt(Password:)
if p ==SECRET:
break

使用MaybeT我们可以在s中编写循环ame方式作为Python代码:

  loop2 :: IO(Maybe())
loop2 = runMaybeT $
永远$
做liftIO $ putStr密码:
p< - liftIO $ getLine
如果p ==SECRET
则mzero - 跳出循环
else return()

最后一个 return() code>继续执行,并且由于我们在永远循环中,控制权返回到do块的顶部。请注意, loop2 可以返回的唯一值是 Nothing ,这对应于跳出循环。



根据情况你可能会发现写 loop2 而不是递归 loop1


Could someone give a super simple (few lines) monad transformer example, which is non-trivial (i.e. not using the Identity monad - that I understand).

For example, how would someone create a monad that does IO and can handle failure (Maybe)?

What would be the simplest example that would demonstrate this?

I have skimmed through a few monad transformer tutorials and they all seem to use State Monad or Parsers or something complicated (for a newbee). I would like to see something simpler than that. I think IO+Maybe would be simple, but I don't really know how to do that myself.

How could I use an IO+Maybe monad stack? What would be on top? What would be on bottom? Why?

In what kind of use case would one want to use the IO+Maybe monad or the Maybe+IO monad? Would that make sense to create such a composite monad at all? If yes, when, and why?

解决方案

This is available here as a .lhs file.

The MaybeT transformer will allow us to break out of a monad computation much like throwing an exception.

I'll first quickly go over some preliminaries. Skip down to Adding Maybe powers to IO for a worked example.

First some imports:

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

Rules of thumb:

In a monad stack IO is always on the bottom.

Other IO-like monads will also, as a rule, always appear on the bottom, e.g. the state transformer 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.

We'll get into what that power is later. For now, get used to thinking of MaybeT IO as the maybe+IO monad stack.

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

Getting used to reading compound type signatures is half the battle to understanding monad transformers.

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

I.e. this works because each statement is in the IO-monad:

 greet :: IO ()                               -- type:
 greet = do putStr "What is your name? "      -- IO ()
            n <- getLine                      -- IO String
            putStrLn $ "Hello, " ++ n         -- IO ()

This will not work because putStr is not in the MaybeT IO monad:

mgreet :: MaybeT IO ()
mgreet = do putStr "What is your name? "    -- IO monad - need MaybeT IO here
            ...

Fortunately there is a way to fix this.

To transform an IO expression into a MaybeT IO expression use liftIO.

liftIO is polymorphic, but in our case it has the type:

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

Now all of the statement in mgreet are from the MaybeT IO monad.

Every monad transformer has a "run" function.

The run function "runs" the top-most layer of a monad stack returning a value from the inside layer.

For MaybeT IO, the run function is:

runMaybeT :: MaybeT IO a -> IO (Maybe a)

Example:

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

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

Also try running:

runMaybeT (forever mgreet)

You'll need to use Ctrl-C to break out of the loop.

So far mgreet doesn't do anything more than what we could do in IO. Now we'll work on an example which demonstrates the power of mixing the Maybe monad with IO.

Adding Maybe powers to IO

We'll start with a program which asks some questions:

 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)

Now suppose we want to give the user the ability to end the survey early by typing END in response to a question. We might handle it this way:

 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))

The problem is that survey1 has the familiar staircasing issue which doesn't scale if we add more questions.

We can use the MaybeT monad transformer to help us here.

 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

Note how all of the statemens in askfor2 have the same monad type.

We've used a new function:

MaybeT :: IO (Maybe a) -> MaybeT IO a

Here is how the types work out:

                  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

Here return is from the IO-monad.

Now we can write our survey function like this:

 survey2 :: IO (Maybe (String,String))
 survey2 =
   runMaybeT $ do a <- askfor2 "name"
                  b <- askfor2 "favorite color"
                  return (a,b)

Try running survey2 and ending the questions early by typing END as a response to either question.

Short-cuts

I know I'll get comments from people if I don't mention the following short-cuts.

The expression:

MaybeT (return (Just r))    -- return is from the IO monad

may also be written simply as:

return r                    -- return is from the MaybeT IO monad

Also, another way of writing MaybeT (return Nothing) is:

mzero

Furthermore, two consecutive liftIO statements may always combined into a single liftIO, e.g.:

do liftIO $ statement1
   liftIO $ statement2 

is the same as:

liftIO $ do statement1
            statement2

With these changes our askfor2 function may be written:

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

In a sense, mzero becomes a way of breaking out of the monad - like throwing an exception.

Another example

Consider this simple password asking loop:

loop1 = do putStr "Password:"
           p <- getLine
           if p == "SECRET"
             then return ()
             else loop1

This is a (tail) recursive function and works just fine.

In a conventional language we might write this as a infinite while loop with a break statement:

def loop():
    while True:
        p = raw_prompt("Password: ")
        if p == "SECRET":
            break

With MaybeT we can write the loop in the same manner as the Python code:

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

The last return () continues execution, and since we are in a forever loop, control passes back to the top of the do block. Note that the only value that loop2 can return is Nothing which corresponds to breaking out of the loop.

Depending on the situation you might find it easier to write loop2 rather than the recursive loop1.

这篇关于用于“傻瓜”的最简单的非平凡monad变换器示例,IO + Maybe的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆