理解这个任务?

时间:2018-06-12 21:14:52

标签: haskell syntax scheme monads interpreter

为了刷新我20岁的Haskell经历,我正在浏览https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Adding_Variables_and_Assignment,并且在某一时刻引入了以下行,以便将op应用于所有参数。这是为了实现例如(+ 1 2 3 4)

numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op

我不懂语法,文中的解释有点模糊。

我理解foldl1的作用以及如何点函数(unpackNum是辅助函数),但使用Monads和>>=运算符让我有点困惑。

如何阅读?

5 个答案:

答案 0 :(得分:8)

基本上,

df = pd.DataFrame({'address': ['NY', 'CA', 'NJ', 'NY', 'WS', 'OR', 'OR'],
               'name1': ['john', 'mayer', 'dylan', 'bob', 'mary', 'jake', 'rob'],
               'name2': ['mayer', 'dylan', 'mayer', 'bob', 'bob', 'tim', 'ben'],
               'comment': ['n/a', 'n/a', 'n/a', 'n/a', 'n/a', 'n/a', 'n/a'],
               'score': [90, 8, 88, 72, 34, 95, 50],
               'selection_status': ['inprogress', 'inprogress', 'inprogress', 'inprogress', 'inprogress', 'inprogress', 'inprogress']})

由两部分组成。

mapM unpackNum params >>= return . Number . foldl1 op 表示:获取参数列表mapM unpackNum params。在每个项目上,应用params:这会在unpackNum monad中生成一个Integer。所以,它不是一个简单的ThrowsError,因为它有机会出错。无论如何,在每个项目上使用Integer成功生成所有unpackNum,或者抛出一些错误。在第一种情况下,我们生成一个类型为Integers的新列表,在第二种情况下,我们(不出所料)抛出错误。因此,此部分的结果类型为[Integer]

第二部分是ThrowsError [Integer]。这里... >>= return . Number . foldl1 op表示:如果第一部分抛出一些错误,整个表达式也会抛出该错误。如果部件成功生成>>=,则继续[Integer],将结果包装为foldl1 op,最后使用Number将此值作为成功计算注入。

总的来说,有monadic计算,但你不应该考虑那些太多。这里的monadic东西只传播错误,或者如果计算成功则存储普通值。有了一点经验,人们可以专注于成功的值,让return处理错误案例。

顺便说一句,请注意虽然本书使用的代码如mapM,>>=,return,但这可能是一种糟糕的风格。人们可以使用相同的效果action >>= return . ffmap f action,这是表达相同计算的更直接的方式。 E.g。

f <$> action

非常接近忽略错误情况的非monadic代码

Number . foldl1 op <$> mapM unpackNum params

答案 1 :(得分:5)

您的问题是关于语法的,所以我将谈谈如何解析该表达式。 Haskell的语法非常简单。非正式地:

  • 由空格分隔的标识符是函数应用程序(应用于其余标识符的第一个标识符)
  • 除了使用非字母数字聊天字符的标识符(例如>>=.)是中缀(即它们的第一个参数位于标识符的左侧)
  • 上面的第一种类型的函数应用程序(非中缀)比第二种
  • 更紧密地绑定
  • 运算符可以向左或向右关联,并具有不同的优先级(使用infix...声明定义)

所以只知道这一点,如果我看到:

mapM unpackNum params >>= return . Number . foldl1 op

首先,我知道它必须像

一样解析
(mapM unpackNum params) >>= return . Number . (foldl1 op)

为了更进一步,我们需要检查我们在这个表达式中看到的两个运算符的固定性/优先级:

Prelude> :info (.)
(.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in ‘GHC.Base’
infixr 9 .
Prelude> :info (>>=)
class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  ...
    -- Defined in ‘GHC.Base’
infixl 1 >>=

(.)具有更高的优先级(9 vs 1 >>=},因此其参数将更紧密地绑定(即我们首先将它们括起来)。但我们怎么知道哪一个是正确的呢?

(mapM unpackNum params) >>= ((return . Number) . (foldl1 op))
(mapM unpackNum params) >>= (return . (Number . (foldl1 op)))

...?由于(.)被声明为infixr,因此它与右侧相关联,这意味着上面的第二个解析是正确的。

正如Will Ness在评论中指出的那样,(.)是关联的(例如加法),因此这两者恰好在语义上是等价的。

对图书馆(或本案例中的Prelude)有一点经验,你学会用操作员正确解析表达式而不用太多考虑。

如果您在完成本练习后想要了解函数的功能或工作方式,那么您可以点击查看您感兴趣的函数的来源并替换左侧的函数 - 右侧的手侧(即内联功能和术语的主体)。显然,你可以在头脑或编辑器中做到这一点。

答案 2 :(得分:4)

你可以把它加糖#34;使用更符合初学者的语法,使用符号。您的函数numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op将成为:

numericBinop op params = do
    x <- mapM unpackNum params -- "<-" translates to ">>=", the bind operator
    return . Number $ foldl1 op x

现在最神秘的是mapM函数,即sequence . fmap,它只需要一个函数, fmaps 就可以在容器上运行,并且翻转类型,在这种情况下(我推测)从[Number Integer]ThrowsError [Integer],同时保留翻转过程中可能出现的任何错误(副作用),换句话说,如果& #39;翻转&#39;导致任何错误,它将在结果中表示。

这不是最简单的示例,您最好看看mapM (fmap (+1)) [Just 2, Just 3]mapM (fmap (+1)) [Just 2, Nothing]的区别。有关更多见解,请查看Traversable类型类。

之后,你绑定 [Integer] monad ThrowsError并将其提供给负责在列表上进行计算的函数,从而产生一个单Integer,在将ThrowsError包裹成return后,您需要使用Number函数重新嵌入URL monad。

如果你仍然无法理解monad,我建议你看一下仍然相关的LYAH chapter,它会轻轻地向你介绍monads

答案 3 :(得分:2)

>>=构建一个可能在任何一端失败的计算:它的左参数可以是一个空的monad,在这种情况下它甚至不会发生,否则它的结果可能为空好。它有类型

>>= :: m a -> (a -> m b) -> m b

参见,它的参数是:沉浸在monad中的值和一个接受纯值并返回沉浸式结果的函数。例如,此运算符是Scala中所谓的flatMap的monadic版本;在Haskell中,它对列表的特定实现称为concatMap。如果您有一个列表l,则l >>= f的工作方式如下:对于l的每个元素,f将应用于此元素并返回一个列表;并且所有这些结果列表被连接起来以产生结果。

考虑Java中的代码:

try {
    function1();
    function2();
}
catch(Exception e) {
}

调用function2时会发生什么?在调用function1之后,程序可能处于有效状态,因此function2()是一个将当前状态转换为下一个不同状态的运算符。但是对function1()的调用可能会导致抛出异常,因此控件会立即转移到catch - 块 - 这可以被视为空状态,因此无法应用{ {1}}到。换句话说,我们有以下可能的控制路径:

function2

(为简单起见,此图中未考虑从[S0] --- function1() --> [S1] --- function2() --> [S2] [S0] --- function1() --> [] --> catch 引发的异常。)

因此,function2是一个(非空)有效机器状态,[S1]将其进一步转换为(非空)有效function2,或者它是空的,因此[S2]是一个无操作,永远不会运行。这可以用伪代码概括为

function2()

答案 4 :(得分:1)

首先,语法。空格是 application ,语义:

f x = f $ x  -- "call" f with an argument x

所以你的表达实际上是

numericBinop op params = ((mapM unpackNum) params) >>= return . Number . (foldl1 op)

接下来,运算符由非字母数字字符构成,没有任何空格。在这里,有.>>=。在GHCi上运行:i (.):i (>>=)会显示其修复程序规范为infixl 9 .infixr 1 >>=9高于1,因此.强于>>=;从而

                       = ((mapM unpackNum) params) >>= (return . Number . (foldl1 op))

infixl 9 .表示.与右侧相关联,因此,最后,它是

                       = ((mapM unpackNum) params) >>= (return . (Number . (foldl1 op)))

(.)定义为(f . g) x = f (g x),因此(f . (g . h)) x = f ((g . h) x) = f (g (h x)) = (f . g) (h x) = ((f . g) . h) x;通过eta-reduction我们有

(f . (g . h)) = ((f . g) . h) 

因此(.)是关联的,因此括号是可选的。我们将明确的parens与&#34;空白&#34;从现在开始申请。因此我们有

numericBinop op params = (mapM unpackNum params)  >>= 
          (\ x -> return (Number (foldl1 op x)))   -- '\' is for '/\'ambda

使用do更容易编写单子序列,以上等同于

      = do 
          {  x <- mapM unpackNum params        -- { ; } are optional, IF all 'do'
          ;  return (Number (foldl1 op x)))    --   lines are indented at the same level
          }

接下来,mapM可以定义为

    mapM f [] = return []
    mapM f (x:xs) = do { x  <- f x ;
                         xs <- mapM f xs ;
                         return (x : xs) }

和Monad Laws要求

      do { r <- do { x ;       ===      do { x ;
                     y } ;                   r <- y ;  
           foo r                             foo r 
         }                                 }

(您可以在我的this recent answer中找到do符号的概述;因此,

numericBinop op [a, b, ..., z] =
   do {
         a <- unpackNum a ;
         b <- unpackNum b ;
         ...........
         z <- unpackNum z ;
         return (Number (foldl1 op [a, b, ..., z]))
      }

(您可能已经注意到我对x <- x绑定的使用 - 我们可以<-的两边使用相同的变量名称,因为monadic绑定是递归 - 从而引入阴影。)

现在希望更清楚了。

但是,我说&#34; 首先,语法&#34;。所以现在,它的意义。同样是Monad Laws,

numericBinop op [a, b, ..., y, z] =
   do {
         xs <- do { a <- unpackNum a ;
                    b <- unpackNum b ;
                    ...........
                    y <- unpackNum y ;
                    return [a, b, ..., y] } ;
         z <- unpackNum z ;
         return (Number (op (foldl1 op xs) z))
      }

因此,我们只需了解两个&#34;计算&#34;,cd的排序,

do { a <- c ; b <- d ; return (foo a b) }
=
          c >>= (\ a ->
                   d >>= (\ b ->     
                       return (foo a b) ))

对于涉及的特定monad,由给定monad的 bind (>>=)运算符的实现决定。

Monads是用于广义函数组合的EDSL。计算的顺序不仅涉及出现在do序列中的显式表达式,还涉及所讨论的特定monad特有的隐含效应,在幕后以原则和一致的方式执行。首先是monad的重点是什么(至少是其中一个要点)。

在这里,涉及的monad似乎关注失败的可能性,以及在失败确实发生的情况下的早期纾困。

因此,使用do代码,我们编写了我们打算发生的本质,并且在幕后,我们会自动处理间歇性故障的可能性。

换句话说,如果其中一个unpackNum计算失败,整个合并的计算也会失败,而不会尝试任何后续的{{ 1}}子计算。但如果所有这些都成功,那么合并计算也会成功。