Scheme: `let` 的语义 (feat. `if` 的语义)


let 的语义

结论: let scope 会立即求值 (包括绑定和绑定之后的表达式, 也就是 let 之外的一层括号所包裹的范围), 即使你把 let 嵌套在内层函数里而且这个函数还完全没有被调用.

示例:

(define (comp x)  
    (if (> 3 x)
        (display "then-clause")
        (display "else-clause")
    )

#|
    (define foo1
        (let ((bar1 (/ 2 0)))    ; * evaluted immediately
            (display "should not be printed")
        )
    )
|#
    (define foo2
        (let ((bar2 (/ 5 2)))    ; * evaluted immediately
            (newline)
            (display "let in `foo2`, bar2: ")
            (display bar2)
        )
    )
)

运行示例:

  • foo1: 可以看到 let 绑定被求值了, 否则不会抛出除零异常.

    prompt> (comp 4)
    else-clause
    ;Division by zero signalled by /.
    ;To continue, call RESTART with an option number:
    ;snip
    
  • foo2: 可以看到 let 绑定后的表达式被求值了, 否则不会有 display 的输出

    prompt> (comp 2)
    then-clause
    let in `foo2`, bar2: 5/2
    ;Unspecified return value
    

if 的语义

if的语义: 根据对条件式进行求值,根据求值结果决定继续对 then/else 分支进行求值.

可以与此对照参考的材料是 SICP (2nd Edition) Exercise 1.6,此题中使用 abstraction ,通过 cond 定义了 new-if:

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

函数应用的语义是(applicative mode evaluation):先对参数(比如这里的then-clauseelse-clause)进行求值,然后进行函数应用,这也是为什么不能在then-clauseelse-clause里写递归表达式,因为它们会先被求值,而不是根据 predicate 的值决定是否求值,于是递归就无穷无尽了. if/cond/… 是 special form, 相较于通过 define 定义的 abstraction, 语义是特殊的. 我喜欢 new-if 这个例子,它简洁而有力地体现了 lisp 元编程的特性,通过此例也可以更好地接纳 lisp 里大量的括号,根据 substitution model, 这里的 predicate, then-clause, else-clause 可以替换成你需要的表达式, 而表达式就是被括号包裹的, 即 括号是表达式的边界, 你可以把被括号包裹的表达式放在任意的参数位 (当然要符合 abstraction 隐式的对于参数的类型约束).

let 立即求值引发的错误实例

对于这样素数判断的一段代码:

(define (prime? x) 
    (if (or (= x 1) (= x 2))
        #t
        test_prime   
    )

    (define (divisible? y)
        (= 0 (remainder x y))
    )
   
    (define (iter_biggest_divisor y)
        (cond ((= y 1) 1)
              ((divisible? y) y)
              (else (iter_biggest_divisor (- y 1)))
        )
    )
    
    (define test_prime
        (let ((biggest_divisor (iter_biggest_divisor (quotient x 2)) )) ; Notice
             (display biggest_divisor)
             (if (= biggest_divisor 1)
                 #t
                 #f
             )
        )
    )
)

运行的结果是:

prompt> (prime? 1)

;The object 0, passed as the second argument to integer-remainder, is not in the correct range.

prompt> (prime? 2)
1
;Value: #t

按照预期, (prime? 1)(prime? 2) 都应该直接返回 #t, 而不是前者报错, 后者呈现出 (display biggest_divisor) 的行为. 根据前文的陈述, 原因是: 整个 let scope 都被求值了.

Wish You a Nice Day!
Built with Hugo
Theme Stack designed by Jimmy