【问题标题】:Translate from monad to applicative从单子翻译成应用
【发布时间】:2013-02-13 22:28:43
【问题描述】:

好的,所以我知道Applicative 类型类包含什么,以及它为什么有用。但我无法完全理解您将如何在一个重要的示例中使用它。

例如,考虑以下相当简单的 Parsec 解析器:

integer :: Parser Integer
integer = do
  many1 space
  ds <- many1 digit
  return $ read ds

如果不使用Monad 实例作为Parser,你会怎么写呢?很多人声称这是可以做到的,而且是个好主意,但我不知道具体是怎么做到的。

【问题讨论】:

    标签: haskell monads applicative


    【解决方案1】:
    integer :: Parser Integer
    integer = read <$> (many1 space *> many1 digit)
    

    或者

    integer = const read <$> many1 space <*> many1 digit
    

    您是否认为其中任何一个更具可读性取决于您。

    【讨论】:

    • 我们要忽略many1 space的值(但不是效果),并将read应用于many1 digit的值。 (对不起,我刚进来,已经很晚了,我累了:我在玩弄术语。)如果你想象sd代表many1 space和@987654329的值分别@,则const read &lt;$&gt; many1 space &lt;*&gt; many1 digit的值(忽略影响)为const read s d = read d
    【解决方案2】:

    我会写

    integer :: Parser Integer
    integer = read <$ many1 space <*> many1 digit
    

    有一堆左关联(如应用程序)解析器构建运算符&lt;$&gt;&lt;*&gt;&lt;$&lt;*。最左边的东西应该是从组件值组装结果值的纯函数。每个运算符右边的东西应该是一个解析器,从左到右共同给出语法的组件。使用哪个运算符取决于两种选择,如下所示。

      the thing to the right is    signal  / noise
      _________________________            
      the thing to the left is \           
                                +-------------------
                        pure /  |   <$>       <$
                      a parser  |   <*>       <*
    

    因此,选择read :: String -&gt; Integer 作为将传递解析器语义的纯函数,我们可以将前导空格分类为“噪声”,将一堆数字分类为“信号”,因此

     read <$ many1 space <*> many1 digit
     (..)    (.........)     (.........)
     pure    noise parser     |
     (.................)      |
         parser              signal parser
     (.................................)
                        parser
    

    您可以结合多种可能性

    p1 <|> ... <|> pn
    

    并用

    表示不可能
    empty
    

    很少需要在解析器中命名组件,生成的代码看起来更像是增加了语义的语法。

    【讨论】:

    • 哇,我知道&lt;$,但我只使用它左边的东西是一个常数,右边是一个简单的值......我从来没有想过会如果我把一个函数放在左边会发生:P 好技巧
    【解决方案3】:

    您的示例可以逐步重写为更类似于 Applicative 的形式:

    do
      many1 space
      ds <- many1 digit
      return $ read ds
    
    1. do 符号的定义:

      many1 space >> (many1 digit >>= \ds -> return $ read ds)
      
    2. $的定义:

      many1 space >> (many1 digit >>= \ds -> return (read ds))
      
    3. .的定义:

      many1 space >> (many1 digit >>= (return . read))
      
    4. 第三单子定律(结合性):

      (many1 space >> many1 digit) >>= (return . read)
      
    5. liftM 的定义(非do 表示法):

      liftM read (many1 space >> many1 digit)
      

    这是(或者应该是,如果我没有搞砸:))在行为上与您的示例相同。

    现在,如果您将liftM 替换为fmap&lt;$&gt;,并将&gt;&gt; 替换为*&gt;,您将获得Applicative:

    read <$> (many1 space *> many1 digit)
    

    这是有效的,因为 liftMfmap&lt;$&gt; 通常应该是同义词,&gt;&gt;*&gt; 也是。

    这一切正常,我们可以这样做,因为原始示例没有使用任何解析器的结果来构建后续解析器。

    【讨论】:

    • 酷!另一种写法read &lt;$ many1 space &lt;*&gt; many1 digit。 :) 最后一句话很重要。是不是说这种风格对应上下文无关文法,更一般的文法必须用monadic风格解析?
    • @WillNess 我不是这方面的专家,但我相信确实如此。
    猜你喜欢
    • 2018-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-28
    • 1970-01-01
    • 2020-02-08
    • 1970-01-01
    • 2017-11-13
    相关资源
    最近更新 更多