1

我正在从“Learn You a Haskell for Great Good!”一书中学习 monads。米兰利波卡。我试图了解单子的结合律。从本质上讲,法律规定,当您拥有一串带有 的单子函数应用程序时>>=,它们的嵌套方式无关紧要。

以下代码使人们能够将类型函数的结果传递给类型a -> m b函数b -> m c

(<=<) :: (Monad m) => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = (\x -> g x >>= f)

但是,对于下面的示例:

ghci> let f x = [x, -x]
ghci> let g x = [x*3, x*2]
ghci> let h = f <=< g
ghci> h 3
[9, -9, 6, -6]

f xg x两者的功能?似乎它们是具有不同 x 值而不是函数的列表。let h = f <=< g该行在上面的代码中是如何工作的?f并且g必须是函数,因为它们与它们一起使用,<=<但我不确定它们是什么。

4

2 回答 2

6
f x = [x, -x]

这是普通的函数定义语法。我们正在定义一个新函数f,写下它在应用于假设值时会产生什么x

let(无论是作为语句还是let ... in ...表达式)只是引入了一个可以进行定义的块,就像where. 定义本身使用与全局定义相同的语法。

如果你知道如何通过在文件中写入来定义函数plusOne n = n + 1,那么这个语法是完全一样的(如果你不知道怎么做,那么我建议在你尝试之前阅读一些关于基本 Haskell 语法的介绍性教程了解一元函数组合)。

所以在这些定义之后fg是函数。f x并且g x没有真正的意义,因为您没有x应用它们的范围。

如果您在范围内确实有这样的值,那么f x将是一个计算结果为列表的表达式,其中涉及调用函数f。这么说f x或者g x是函数仍然是不正确的。

所以现在应该清楚的是,通过将运算符应用于andlet h = f <=< g来定义一个新值。h<=<fg

于 2019-08-05T05:39:01.050 回答
4

没有什么比在一张纸上手工完成定义更能获得理解的感觉了。

f x = [x, -x]也可以写f = (\ x -> [x, -x])。因此

  h 3 
= {- by def of h -}
  (f <=< g) 3 
= {- by def of (<=<) -}
  (\x -> g                   x >>= f               ) 3
= {- by defs of f and g -}
  (\x -> (\ x -> [x*3, x*2]) x >>= (\ x -> [x, -x])) 3
= {- by substitution -}
         (\ x -> [x*3, x*2]) 3 >>= (\ x -> [x, -x])
= {- by substitution -}
                 [3*3, 
                  3*2]         >>= (\ x -> [x, -x])
= {- by definition of (>>=) for [] -}
  concat [       (3*3)         &   (\ x -> [x, -x])  -- x & f == f x
         ,       (3*2)         &   (\ x -> [x, -x]) 
         ]
= {- by definition of concat -}
                 (3*3)         &   (\ x -> [x, -x])
         ++      (3*2)         &   (\ x -> [x, -x]) 
= 
  [9, -9, 6, -6]

编辑)有关这些Kleisli 箭头及其可组合性的图片以及更多讨论,请参阅我的这个较旧的答案

于 2019-08-05T06:18:05.787 回答