函数组合 (.) 如何从内部工作?

5

我正在学习Haskell。目前,我正在学习函数组合。 我至少在基础层面上理解了如何使用函数(.),但有两个方面我还不明白。

所以这个函数长这样:

(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)

首先是类型声明。 (b -> c) ->(a -> b) 基本上意味着函数 f 接受来自函数 g 的结果值(b),该函数接受值 a 并返回类型为 c 的值。我不理解以下部分 -> a -> c,为什么应该有 -> a?为什么 (b -> c) ->(a -> b) -> c 是错误的?从我的角度来看(显然是错误的),函数 g 已经将 a 作为参数。
其次是函数 f . g = \x -> f (g x) 的主体。这里的 \x -> 做什么?Lambda 很简单。例如:filter (\(a,b) -> a + b > 4) [(1,2),(3,4)],但一个简单的 \x -> 让我卡住了。我可能会像这样编写主体 f . (g x) = f (g x)(这显然是错误的)。

2
也许将类型写成(.) :: Fun1 -> Fun2 -> Fun3 更容易阅读,即(.)接受一个类型为Fun1(一个函数)的参数,然后是另一个类型为Fun2(另一个函数)的参数,并返回一个类型为Fun3(另一个函数,即“组合”函数)的值。具体来说,Fun1 = (b->c)Fun2=(a->b)Fun3 = (a->c)。实际上,在您的类型周围添加括号--它们是隐式的。 - chi
对于你的语法问题,\ x-> 并不存在单独存在的情况;这里有一个隐含的括号,它会尽可能地向右移动,因此 f . g = \ x -> f (g x) 实际上是 f . g = (\ x -> f (g x) ),一个 lambda 函数的符号表示。 - Will Ness
1个回答

8
(b -> c) -> (a -> b) -> c 是一个函数,接受两个函数 f :: b -> cg :: a -> b,并在不使用类型为 a 的初始参数的情况下调用 g

对于第二个问题,考虑如何使用前缀表示法定义 (.)。如果我们使用“常规”的函数名称,可能更容易理解;我将在每段代码后面包含注释:

(.) f g x = f (g x)    -- compose f g x = f (g x)

x(.)的“第三个参数”,或者更准确地说是(.) f g返回的函数的参数。这等同于通过将函数放在右侧而不是该函数的最终返回值上,直接定义(.) f g作为一个函数:

(.) f g x =       f (g x)  -- Implicit function def: compose f g x =       f (g x)
(.) f g   = \x -> f (g x)  -- Explicit function def: compose f g   = \x -> f (g x)

您也可以使用括号隐式地定义函数:

(f . g) x = f (g x)

使用显式 lambda 来定义它的原因与 GHC 编译器中程序优化的微妙细节有关。作为初学者,你绝对不需要担心那些东西,但编写关键库函数的人会考虑它,以便你的代码运行更快。 - dfeuer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接