关于功能构成和部分应用的直观思考方式

时间:2018-03-25 14:04:27

标签: javascript closures scheme sicp function-composition

这是我在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但是我无法直观地理解为什么。我理解功能组成和部分应用,但不是在这种背景下。

这里发生了什么?

3 个答案:

答案 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时,您会得到您期望的行为,并且每次调用的确实会加倍。

如果不是很清楚我很抱歉,我找不到直观的解释方法。