ruby - 在 Lisp/Ruby 中将 IF-ELSE 实现为过程

标签 ruby lambda lisp sicp

我正在阅读 Understanding Computation 的 lambda 演算部分(第 4 章) .此代码尝试使用 Ruby 中的 lambda 演算构建 MOD:

MOD = -> m { -> n { CONTROL_IF[
            IS_LESS_OR_EQUAL[n][m]
            ][
                MOD[SUBTRACT[m][n]][n]
            ][
                m
            ]
        }}

CONTROL_IF = -> b { b }
IS_LESS_OR_EQUAL = -> m { -> n { IS_ZERO[SUBTRACT[m][n]] }}
IS_ZERO = -> n { n[-> x { B_FALSE }][B_TRUE] }
#Full code would listed below

irb 失败并打印 SystemStackError: stack level too deep

当输入 to_integer(MOD[THREE][TWO])

根据书上的说法,MOD 不工作,因为它未能实现 Ruby if-else 控制逻辑的惰性求值,并不断递归调用自身。

并且修改后有效:

#working one
MOD = -> m { -> n { CONTROL_IF[
            IS_LESS_OR_EQUAL[n][m]
            ][
              -> x { MOD[SUBTRACT[m][n]][n][x]}
            ][
              m
            ]
        }}

据说 -> x { ...[x]} 延迟了 MOD 的递归调用,但我不明白。

此外,我想到了 SICP 中的类似练习(1.6):

(define (new-if predicate then-clause else-clause)
    (cond (predicate then-clause)
        (else else-clause)))

(define (sqrt-iter guess x)
    (new-if (good-enough? guess x)
            guess
            (sqrt-iter (improve guess x) x)))

作为用户定义的过程,sqrt-iter 会遇到无限递归,因为 (sqrt-iter (improve guess x) x) 每次都作为参数求值过程new-if

所以现在我明白了将 IF-ELSE 作为过程实现的关键点是惰性求值,我的问题是:

  1. 为什么 MOD 修改后可以正常工作?
  2. new-if 能否以 Ruby 方式进行改进 (-> x { ...[x]} ) 以便延迟递归调用?

Ruby 的完整代码:

ZERO  = -> proc { -> x {                          x      } }
ONE   = -> proc { -> x {                     proc[x]     } }
TWO   = -> proc { -> x {                proc[proc[x]]    } }
THREE = -> proc { -> x {           proc[proc[proc[x]]]   } }
FOUR  = -> proc { -> x {      proc[proc[proc[proc[x]]]]  } }
FIVE  = -> proc { -> x { proc[proc[proc[proc[proc[x]]]]] } }

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

CONTROL_IF = -> b { b }

B_TRUE  = -> x { -> y { x }}
B_FALSE = -> x { -> y { y }}
IS_ZERO = -> n { n[-> x { B_FALSE }][B_TRUE] }

IS_LESS_OR_EQUAL = -> m { -> n { IS_ZERO[SUBTRACT[m][n]] }}

ADD      = -> m { -> n { n[INCREMENT][m] } }
SUBTRACT = -> m { -> n { n[DECREMENT][m] } }

PAIR  = -> x { -> y { -> f { f[x][y] } } }
LEFT  = -> p { p[-> x { -> y { x } } ] }
RIGHT = -> p { p[-> x { -> y { y } } ] }

INCREMENT = -> n { -> p { -> x { p[n[p][x]] } } }

SLIDE     = -> p { PAIR[RIGHT[p]][INCREMENT[RIGHT[p]]] }
DECREMENT = -> n { LEFT[n[SLIDE][PAIR[ZERO][ZERO]]] }

=begin
#failed one
MOD = -> m { -> n { CONTROL_IF[
            IS_LESS_OR_EQUAL[n][m]
            ][
                MOD[SUBTRACT[m][n]][n]
            ][
                m
            ]
        }}
=end

#working one
MOD = -> m { -> n { CONTROL_IF[
            IS_LESS_OR_EQUAL[n][m]
            ][
              -> x { MOD[SUBTRACT[m][n]][n][x]}
            ][
              m
            ]
        }}

#to_integer(MOD[THREE][TWO])

最佳答案

我不太熟悉 Ruby 语法,但您似乎将匿名函数放置在表达式所在的位置。匿名函数确实会产生所需的延迟,因为它在调用之前不会计算函数体。问题是如何调用它以及在何处调用它。引入这样的“就地”延迟通常会导致非常困惑的代码。

在 Scheme 中你可以制作一个延迟版本的 if 但语义略有不同:

(define (new-if test consequent alternative)
       (cond (test (consequent))
             (else alternative)))
> (new-if (= (+ 1 2) 3) 
                 (lambda () (display "Yay"))
                 (lambda () (display "Nei")))
Yay

因此,后项和替代项需要作为匿名函数传递。注意,这个版本仍然使用特殊形式 cond。如果你想一直深入到基础知识并通过 lambda 函数在一种具有应用评估顺序的语言中定义 if(即像 Scheme 一样严格),那么你将面临一项非常艰巨的任务控制表达式在何处延迟和不延迟。

关于ruby - 在 Lisp/Ruby 中将 IF-ELSE 实现为过程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35027617/

相关文章:

ruby-on-rails - 如何在 Rails 应用程序中测试简单的 Ruby 类?

arrays - Ruby if else in array.each

c++ - 在 unordered_map<vector<T>> 中使用 lambda 函数进行散列

arrays - 如何连接简单数组并在 Common Lisp 中保留元素类型 '(unsigned-bytes 8)?

# 符号的 LISP 含义

ruby - 如何降级 jekyll 以使用 github 页面?

对具有无界通配符类型的方法参数使用泛型 lambda 的 Java 编译错误

lambda - Elm 中是否有任何等价的占位符?

scheme - 对于应用顺序,参数的评估顺序是什么?从左到右还是从右到左?

ruby - compass:dist & execFile ("compass.bat", ...) - 警告:生成 EPERM