这是我在SICP中发现的一个问题,已翻译成JavaScript。
let double = function(f) {
return function(x) {
return(f(f(x)))
}
}
let succ = x => x + 1
let ans = double(double)(double)(succ)(0)
console.log(ans) // What's the output?
这是我的思考过程:
将double
应用于double
会产生一个使给定函数翻两番的函数。
将double
提供给此四重函数会产生一个将给定函数应用8次的函数。
因此,结果应该是8.但结果是16。
通过替换双重函数并用暴力解决它,我得到16但是我无法直观地理解为什么。我理解功能组成和部分应用,但不是在这种背景下。
这里发生了什么?
答案 0 :(得分:3)
TL; DR:功能组合就像乘法。平方 4 次意味着将其提升到 16 次幂。 double
应该已命名为 squared
。
使用等式表示法非常:
double(f)(x) = f(f(x)) = (f . f)(x) -- (f^2)(x) ... why? see below:
,其中
(f . g)(x) = f(g(x))
按定义。然后,将上述等式的右侧替换为左侧,甚至将左侧替换为右侧,因为它适合我们,我们有
double(double)(double)(succ)(0) -- double(double) =
=
(double . double)(double)(succ)(0) -- = double^2
=
double(double(double))(succ)(0) -- (double^2)(double) =
=
double((double . double))(succ)(0) -- = double(double^2)
=
((double . double) . (double . double))(succ)(0) -- = double^4 !!
(不涉及 succ
!)。从f = g
派生f(x) = g(x)
称为 eta-contraction 。
功能组合是关联的,
((f . g) . h)(x) = (f . g)(h(x)) = f(g(h(x)))
= f( (g . h)(x) )
= (f . (g . h))(x)
所以我们继续
((double . double) . (double . double))(succ)(0)
=
(double . double . double . double)(succ)(0) -- = f . f . f . f = f^4
=
double(double(double(double(succ))))(0) -- = (double^4)(succ)
=
double(double(double(succ^2)))(0) -- = (double^3)(succ^2) , succ^2 = succ . succ
=
double(double(succ^4))(0) -- = (double^2)(succ^4)
=
double(succ^8)(0) -- = (double^1)(succ^8)
=
(succ^16)(0) -- = (double^0)(succ^16) = identity(succ^16) = succ^16
=
16
所以这归结为不混淆(f . g)
和f(g)
。
此外,倍增乘法链意味着平方。
答案 1 :(得分:2)
这真的是一个关于关联性的问题。在Racket中(在JS中也是如此),比较这些:
app
所以唯一的区别是函数应用程序是左关联的。
如果你拿走你的JS并添加更多的parens来强制权利相关性,你应该看到" 8"你期待的。
答案 2 :(得分:1)
这不是最容易解释的事情,但我会尽我所能。 我将使用Scheme,因为这是我熟悉的。
(define (double f)
(lambda (x)
(f (f x))))
(define (succ x)
(+ x 1))
(double double)
; evaluates to
(lambda (x)
(double (double x)))
; so
((double double) succ)
; is the same as
(double (double succ))
; which is the same as
(double (lambda (x)
(succ (succ x))))
; lets call that last lambda inc2
; the last statement then equals
(inc2 (inc2 x))
; so when we call
(((double double) succ) 0)
; we get 4 calls to succ, and the result is 4
; now let's add another double
((double double) double)
; this evaluates to
((lambda (x)
(double (double x)))
double)
; or
(double (double double))
; or
(double (lambda (x)
(double (double x))))
; lets call that last lambda quadruple
(double quadruple)
; or
(lambda (x)
(quadruple (quadruple x)))
; 4x4 is 16 and so when we call
((((double double) double) succ) 0)
; we call succ 16 times
; this in contrast to
((double (double (double succ))) 0)
; which will in fact return 8
归结为你打电话给双人的方式。 每次使用参数double调用double时,调用量增加到2 ^(调用double的次数)。 当您在已经加倍的函数上调用double时,您会得到您期望的行为,并且每次调用的确实会加倍。
如果不是很清楚我很抱歉,我找不到直观的解释方法。