我读过g :: A - >的组成。 B和f :: B - > C,发音(“f由g组成”),导致A - >的另一个函数(箭头)。 C.这可以更正式地表达为
f•g = f(g)=组成::(B - > C) - > (A - > B) - > (A - > C)
上述成分是否也可以定义如下?请澄清。 在这种情况下,compose函数采用相同的两个函数f和g,并从A - >返回一个新函数。下进行。
f•g = f(g)=组成::((B - > C),(A - > B)) - > (A - > C)
答案 0 :(得分:8)
首先,我们需要做一些正确的事情:
f ○ g
表示与f(g)
完全不同的内容。
x
,首先将其提供给g
,然后将结果传递给f
,输出最终结果,即f(g(x))
。f(g)
表示您立即将函数f
应用于值 g
,而无需等待任何参数。 (g
碰巧有一个函数类型,但在函数式语言中,函数可以像任何其他值/参数一样传递。)除非你正在处理一些非常古怪的多态函数,否则其中一个将是错误的类型。例如,类型很好的组合可能是
sqrt ○ abs :: Double -> Double
而良好类型的应用程序可能是(至少在Haskell中)
map(sqrt) :: [Double] -> [Double]
我将在下面假设您正在谈论f ○ g
。
必须为一个函数本身提供类型签名,而不是一个应用于某些参数的函数。这是很多人完全错误的事情:在f(x)
中,你有一个函数f
和一个参数x
。但是f(x)
不是一个函数,只是将函数应用于值的结果值!所以,你不应该写f ○ g :: ...
之类的内容(除非你实际上只是讨论了结果的类型)。最好只写○ :: ...
(或者,在Haskell中,(○) :: ...
)。
功能箭头不是关联的。大多数数学家可能甚至不知道X -> Y -> Z
应该是什么意思。像Haskell这样的语言意味着什么可能实际上有点令人惊讶:
X -> Y -> Z ≡ X -> (Y -> Z)
即。这是一个函数的类型,它首先只接受X
类型的参数。结果将再次成为一个函数,但只接受Y
类型的参数。如果您愿意,此函数将具有已内置的X
值(在所谓的closure中,除非编译器对此进行优化)。给它Y
值也可以让函数真正完成它的工作并最终产生Z
结果。
此时你已经得到了答案:确实签名X -> Y -> Z
和(X, Y) -> Z
基本相同。重写此过程的过程称为currying。
特别回答您的问题:大多数语言通常不做任何调整,因此签名((B -> C), (A -> B)) -> (A -> C)
实际上更正确。它对应于您可以调用的函数
compose(f,g)
OTOH,咖喱签名(B -> C) -> (A -> B) -> (A -> C)
意味着您需要逐个提供参数:
compose(f)(g)
只有像Haskell这样的语言才是标准风格,但你不需要那些parens:以下所有内容在Haskell中解析相同
compose(f)(g)
compose f g
(compose) f g
(.) f g
f . g
其中.
实际上是composition operator,您可以从文档中看到类型
(.) :: (b -> c) -> (a -> b) -> a -> c
答案 1 :(得分:4)
由于您使用 Javascript 标记了您的问题,因此从Javascript的角度来看是一个答案。
假设我正确理解您的签名,您需要按如下方式调整组合函数:(f, g) => x => f(g(x));
。当然,这有效,但你失去了灵活性,并且没有任何收获。
原始咖喱功能以咖喱形式定义,这意味着它总是需要一个参数。如果整个代码中的每个函数只需要一个参数,那么就没有更多的arity(在大多数情况下)。它被抽象掉了。 Currying有助于函数组合,因为函数总是返回单个值。 Curried函数就像构建块。你几乎可以把它们放在一起:
const comp = f => g => x => f(g(x)),
comp2 = comp(comp)(comp),
add = y => x => x + y,
inc = x => x + 1,
sqr = x => x * x;
console.log(comp(sqr)(inc)(2)); // 9
console.log(comp(add)(sqr)(2)(3)); // 7
console.log(comp2(sqr)(add)(2)(3)); // 25

正如你只能在后一种情况下看到的那样,我们必须考虑这个问题。
如果对代码库的每个功能始终如一地应用Currying,那么它只能发挥其优势,因为它具有系统效应。
答案 2 :(得分:3)
首先,更常用的是圆圈:f ∘ g
。
其次,它更适合发音为“f g”。 (“f由g组成”听起来像f
,由g
组成,而不是由两者组成的新函数。)
最后,这两种类型基本相同,区别仅在于您希望如何将函数传递给compose
函数。第一个定义完全curried函数的类型,这样compose
将一个函数作为参数,并返回一个新函数,它将第二个函数作为参数并返回组合。这意味着使用f :: B -> C
和g :: A -> B
,您可以定义(使用Haskell语法)
compose :: (B -> C) -> (A -> B) -> (A -> C)
compose f g = \x -> f (g x)
或未经证实的版本
compose' :: ((B -> C), (A -> B)) -> (A -> C)
compose' (f, g) = \x -> f (g x)
无论哪种方式,返回值都是一样的;唯一的区别在于参数的传递方式。你可以写h = compose f g
或者你可以写h = compose' (f, g)
。