编写函数签名
Compose function signature
我读到 g :: A -> B 和 f :: B -> C 的组合,发音为“f composed of g”,导致另一个函数(箭头)从 A -> C. 这可以更正式地表达为
f • g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)
上面的组合是否也可以定义如下?请说清楚。
在这种情况下,compose 函数采用相同的两个函数 f 和 g 以及 return 来自 A -> C 的新函数。
f • g = f(g) = compose :: ((B -> C), (A -> B)) -> (A -> C)
首先比较常用的是空心圆:f ∘ g
.
其次,它的发音更恰当"f composed with g"。 ("f composed of g" 听起来像 f
是由 g
组成的,而不是一个由两者组成的新函数。)
最后,这两种类型在本质上是相同的,不同之处仅在于您期望将函数传递给 compose
函数的方式。第一个定义了完全柯里化函数的类型,这样 compose
接受一个函数作为参数,return 是一个接受第二个函数作为参数的新函数,return 是组成。这意味着使用 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)
无论哪种方式,return 值都是相同的;唯一的区别在于参数的传递方式。你可以写 h = compose f g
或者你可以写 h = compose' (f, g)
.
首先我们需要做对一些事情:
f ○ g
与 f(g)
.
的意思完全不同
- 前者是一个函数,给定一个参数
x
,首先将它提供给g
,然后将结果传递给f
,并输出最终结果,即f(g(x))
.
- OTOH,
f(g)
意味着您立即将函数 f
应用于 value 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)
实际上更正确。它对应一个你可以调用的函数 as
compose(f,g)
OTOH,curried 签名 (B -> C) -> (A -> B) -> (A -> C)
意味着您需要一个一个地输入参数:
compose(f)(g)
只有像 Haskell 这样的语言才是标准样式,但你不需要那里的括号:以下所有内容在 Haskell
中的解析相同
compose(f)(g)
compose f g
(compose) f g
(.) f g
f . g
其中 .
实际上是 composition operator,正如您从文档中看到的那样,其类型为
(.) :: (b -> c) -> (a -> b) -> a -> c
由于您用 Javascript 标记了您的问题,这里是从 Javascript 的角度回答的。
假设我正确理解了您的签名,您希望按如下方式调整组合函数:(f, g) => x => f(g(x));
。当然,这行得通,但你失去了灵活性并获得了嗯,什么都没有。
原来的 curry 函数是以 curried 形式定义的,这意味着它总是需要一个参数。如果整个代码中的每个函数都只需要一个参数,那么就没有更多的参数(好吧,在大多数情况下)。它被抽象掉了。柯里化有利于函数组合,因为函数总是 return 单个值。咖喱函数就像积木一样。您几乎可以用任何方式将它们放在一起:
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
正如您所看到的,只有在后一种情况下,我们才必须考虑数量。
只有始终如一地应用于代码库的每个功能,柯里化才能发挥其优势,因为它具有系统效应。
我读到 g :: A -> B 和 f :: B -> C 的组合,发音为“f composed of g”,导致另一个函数(箭头)从 A -> C. 这可以更正式地表达为
f • g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)
上面的组合是否也可以定义如下?请说清楚。 在这种情况下,compose 函数采用相同的两个函数 f 和 g 以及 return 来自 A -> C 的新函数。
f • g = f(g) = compose :: ((B -> C), (A -> B)) -> (A -> C)
首先比较常用的是空心圆:f ∘ g
.
其次,它的发音更恰当"f composed with g"。 ("f composed of g" 听起来像 f
是由 g
组成的,而不是一个由两者组成的新函数。)
最后,这两种类型在本质上是相同的,不同之处仅在于您期望将函数传递给 compose
函数的方式。第一个定义了完全柯里化函数的类型,这样 compose
接受一个函数作为参数,return 是一个接受第二个函数作为参数的新函数,return 是组成。这意味着使用 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)
无论哪种方式,return 值都是相同的;唯一的区别在于参数的传递方式。你可以写 h = compose f g
或者你可以写 h = compose' (f, g)
.
首先我们需要做对一些事情:
的意思完全不同f ○ g
与f(g)
.- 前者是一个函数,给定一个参数
x
,首先将它提供给g
,然后将结果传递给f
,并输出最终结果,即f(g(x))
. - OTOH,
f(g)
意味着您立即将函数f
应用于 valueg
,无需等待任何参数。 (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)
实际上更正确。它对应一个你可以调用的函数 as
compose(f,g)
OTOH,curried 签名 (B -> C) -> (A -> B) -> (A -> C)
意味着您需要一个一个地输入参数:
compose(f)(g)
只有像 Haskell 这样的语言才是标准样式,但你不需要那里的括号:以下所有内容在 Haskell
中的解析相同 compose(f)(g)
compose f g
(compose) f g
(.) f g
f . g
其中 .
实际上是 composition operator,正如您从文档中看到的那样,其类型为
(.) :: (b -> c) -> (a -> b) -> a -> c
由于您用 Javascript 标记了您的问题,这里是从 Javascript 的角度回答的。
假设我正确理解了您的签名,您希望按如下方式调整组合函数:(f, g) => x => f(g(x));
。当然,这行得通,但你失去了灵活性并获得了嗯,什么都没有。
原来的 curry 函数是以 curried 形式定义的,这意味着它总是需要一个参数。如果整个代码中的每个函数都只需要一个参数,那么就没有更多的参数(好吧,在大多数情况下)。它被抽象掉了。柯里化有利于函数组合,因为函数总是 return 单个值。咖喱函数就像积木一样。您几乎可以用任何方式将它们放在一起:
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
正如您所看到的,只有在后一种情况下,我们才必须考虑数量。
只有始终如一地应用于代码库的每个功能,柯里化才能发挥其优势,因为它具有系统效应。