【问题标题】:I am having trouble understanding this lambda calculus from Understanding Computation我在理解计算中无法理解这个 lambda 演算
【发布时间】:2014-12-14 03:51:59
【问题描述】:

在 Tom Stuart 的《Understanding Computation》一书中,有一章专门介绍了通过 Procs/Lambdas 重建 FizzBu​​zz。他首先讨论了如何通过 procs 定义数字,并表明“数字”可以由运行 x 次数的 proc 来表示它所代表的数字(即 1 是 proc[x], 2是 proc[proc[x]] 等)。这就说得通了。然而,下一点他定义了一个to_integer 方法:

def to_integer(proc)
  proc[-> n { n + 1 }][0]
end

这让我的大脑崩溃了。我无法理解这意味着什么。诚然,除了在使用 rails 时范围内的 lambdas 之外,我使用 procs 的次数不多。

我可以把它简化成

def to_integer(proc)
  proc[Proc.new(n){ n + 1 }][0]
end

...但这是我所能得到的。谁能以更容易理解的方式解释或简化这一点?这里发生了什么?我对 proc[x][0] 样式感到困惑,n 来自哪里。我只是很难理解这一点。

在书中,传递给这个方法的过程是这样的:

ZERO = -> p { -> x { x } }
ONE  = -> p { -> x { p[x] } }
TWO  = -> p { -> x { p[p[x]] } }

【问题讨论】:

    标签: ruby lambda


    【解决方案1】:

    proc 字面量的语法为“-> parameters { body }”。增加数字的 proc 文字的具体示例是“-> n { n + 1 }”。为了可读性,可以将 proc 文字分配给变量,例如“增量 = -> n { n + 1 }”。

    可以使用方括号“-> parameters { body }[arguments]”来调用 proc 字面量。使用前面的例子,这里是如何增加 9 给你 10 "-> n { n + 1 }[9]"。传入的参数“9”由body中的参数“n”表示,“n”加1给出结果。同样,为了便于阅读,可以使用“INCREMENT[9]”来给出相同的结果。

    现在让我们看看“ZERO = -> p { -> x { x } }”。当执行“ZERO[INCREMENT][0]”时,首先执行“ZERO[INCREMENT]”部分。结果是“-> x { x }”,通过替换,原始语句变为“-> x { x }[0]”。执行时,它返回一个 0。

    现在让我们看看“ONE = -> p { -> x { p[x] } }”。当执行“ONE[INCREMENT][0]”时,首先执行“ONE[INCREMENT]”部分。结果是“-> x { p[x] }”,其中 p 是“增量”。通过替换,原始语句变为“-> x { INCREMENT[x] }[0]”。执行时,基本上INCREMENT是用0调用的,即“INCREMENT[0]”,结果是1。

    最后,我们来看看: def to_integer(proc) 过程[-> n { n + 1 }][0] 结束

    “to_integer(ZERO)”执行“ZERO[-> n { n + 1 }][0]”。注意“-> n { n + 1}”与INCREMENT相同,因此执行与ZERO[INCREMENT][0]相同。如上所述,这会产生 0。

    “to_integer(ONE)”执行“ONE[-> n { n + 1 }][0]”。注意“-> n { n + 1}”和INCREMENT一样,所以执行和ONE[INCREMENT][0]一样。如上所述,这会产生 1。

    同样的过程可以应用于TWO等

    【讨论】:

      【解决方案2】:

      to_integer 中的代码使用 Ruby 的 [] 简写来调用 proc 链接 2 个 proc 调用。

      在 IRB 中手动浏览它会很有帮助。让我们从定义零(一个 lambda)开始:

      ZERO = -> p { -> x { x } }
      => #<Proc:0x007ff86a147930@(irb):103 (lambda)>
      

      您会在这里看到不同的数字,但重要的是 ZERO 是一个 lambda,它是一个 proc。

      让我们给 to_integer 中的匿名 lambda 起一个名字,以便清楚那里发生了什么。使用“stabby lambda”语法:

      INCR = -> n { n + 1 }
      INCR[2]
      => 3 
      INCR[3]
      => 4
      

      我们发送一个数字,它返回该数字加 1。

      然后我们将 ZERO 传递给 to_integer,它通过调用 lambda ZERO 并发送 INCR 来启动:

      ZERO[INCR]
      => #<Proc:0x007ff86a0decf0@(irb):103 (lambda)> 
      

      如您所见,这会返回另一个 lambda(在 ZERO 中定义的一个,其中 p 设置为 INCR)。我们现在可以通过调用这个 lambda 并发送 0 来开始滚动:

      ZERO[INCR][0]
      => 0 
      

      【讨论】:

        【解决方案3】:

        这里的基本思想实际上来自于在皮亚诺公理中构造自然数 (http://en.wikipedia.org/wiki/Peano_axioms)

        因此,0 是 0,1 是 S(0),2 是 S(S(0)),3 是 S(S(S(0))) 等等(其中 S 是某个函数)。

        其次这个方法可以重写,用[]代替call

        def to_integer(proc)
          proc.call(Proc.new { |n| n + 1 }).call(0)
        end
        

        我们现在可以清楚地看到它进行反向计算 - 当我们尝试计算有多少是一时(实际上是 S(0),或者在我们的符号 -&gt; p { -&gt; x { p[x] } } 中,它相当于 Proc.new { |p| Proc.new { |x| p.call(x) } },它存储信息关于它需要 S(x),我们不知道的东西在哪里(我们的 proc 的参数)

        现在是棘手的部分。这里 Ruby 调用我们定义的一个(它是一个 Proc,所以它可以被调用!)作为参数传递另一个 Proc - 这个等同于 S 函数 (n + 1)。所以在最终.call(0) 之前,我们有一系列 Procs - 调用更深的一个,参数增加 1(因为我们的整数) - S(x)。通过 final call(0) 我们设置自然数 S(0) 的基数。

        也许考虑一些例子会有所帮助:

        to_integer 替换为:

        def to_integer(proc)
          proc[-> n { n + 1 }][0]
        end
        

        (有趣的是,这些数字与标准自然数一样好)。

        如果我们将调用中的基数从 0 更改为 1,会发生什么?

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-05
          • 1970-01-01
          • 1970-01-01
          • 2014-10-17
          相关资源
          最近更新 更多