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

时间:2016-01-27 02:10:33

标签: 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. 可以用Ruby方式改进 new-if -> x { ...[x]} ),以便延迟递归调用吗?
  3. 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])
    

1 个答案:

答案 0 :(得分:-1)

我对Ruby语法并不熟悉,但似乎你正在放置表达式的匿名函数。匿名函数确实产生了所需的延迟,因为它在调用之前不会对body进行求值。问题是如何调用它以及在何处调用它。介绍这样一个"就地"延迟会导致代码非常混乱。

在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),你就要设置一个非常困难的任务来控制表达式被延迟,而不是表达式。