【问题标题】:Boolean operators tail-call optimized?布尔运算符尾调用优化?
【发布时间】:2015-09-14 16:13:12
【问题描述】:

在学习 Racket 并开始进行一般编程时,我以两种不同的方式定义 ormap

(define (or-map proc lst)
  (cond
    [(null? lst) #false]
    [(proc (car lst)) #true]
    [else (or-map proc (cdr lst))]))


(define (or-map proc lst)
  (if (null? lst)
      #false
      (or (proc (car lst)) ; <-- this one
          (or-map proc (cdr lst)))))

想到以下问题:

第二个尾调用优化了吗?我不确定注释行是否被丢弃(或 (or ...) 堆叠它的参数),因为如果它是 #true 函数调用结束,如果它是 #false 它应该是与 (or ...) 语句的进一步评估无关。

所以我运行了以下测试,并查看了任务管理器的内存使用情况:

(define (test)
  (or #f
      (test)))

(test)

记忆保持不变。所以我想我可以得出结论 (or ...*) 得到尾调用优化?我认为对于 (and ...*) 和其他布尔运算符保持不变,但是当我将 (test) 中的 or 更改为也不内存已满。

总之,是吗

  • 到目前为止我的结论有一些错误吗?
  • nor-test 发生了什么?
  • 正确地假设我对 or-map 的两个定义在性能方面是等效的,还是一个比另一个更可取?
  • (在这种情况下,我对任务管理器的使用是否合法?当内存填满 stackoverflow 时,我在其中看到的现象是什么?)

【问题讨论】:

    标签: stack-overflow racket boolean-operations tail-call-optimization


    【解决方案1】:

    鉴于您的用户名是Tracer,您可能会觉得可以使用racket/trace 来检查它很有趣。 :)

    首先是您希望使用尾部消除而不是使用尾部消除的函数示例:

    #lang racket
    
    (define (tail-call xs [results '()])
      (match xs
        [(list) results]
        [(cons x xs) (tail-call xs (cons x results))]))
    
    (define (no-tail-call xs)
      (match xs
        [(list) (list)]
        [(cons x xs) (cons x (no-tail-call xs))]))
    

    我们可以trace他们并看到这反映在通话深度中:

    (require racket/trace)
    (trace tail-call no-tail-call)
    
    (tail-call '(1 2 3 4 5))
    ;; >(tail-call '(1 2 3 4 5))
    ;; >(tail-call '(2 3 4 5) '(1))
    ;; >(tail-call '(3 4 5) '(2 1))
    ;; >(tail-call '(4 5) '(3 2 1))
    ;; >(tail-call '(5) '(4 3 2 1))
    ;; >(tail-call '() '(5 4 3 2 1))
    ;; <'(5 4 3 2 1)
    ;; '(5 4 3 2 1)
    
    (no-tail-call '(1 2 3 4 5))
    ;; >(no-tail-call '(1 2 3 4 5))
    ;; > (no-tail-call '(2 3 4 5))
    ;; > >(no-tail-call '(3 4 5))
    ;; > > (no-tail-call '(4 5))
    ;; > > >(no-tail-call '(5))
    ;; > > > (no-tail-call '())
    ;; < < < '()
    ;; < < <'(5)
    ;; < < '(4 5)
    ;; < <'(3 4 5)
    ;; < '(2 3 4 5)
    ;; <'(1 2 3 4 5)
    ;; '(1 2 3 4 5)
    

    接下来让我们为or-map 的两个定义执行此操作。我们看到两者都具有相同的扁平形状:

    (define (or-map/v1 proc lst)
      (cond
        [(null? lst) #false]
        [(proc (car lst)) #true]
        [else (or-map/v1 proc (cdr lst))]))
    
    (define (or-map/v2 proc lst)
      (if (null? lst)
          #false
          (or (proc (car lst)) ; <-- this one
              (or-map/v2 proc (cdr lst)))))
    
    (trace or-map/v1 or-map/v2)
    
    (or-map/v1 even? '(1 1 1 2))
    ;; >(or-map/v1 #<procedure:even?> '(1 1 1 2))
    ;; >(or-map/v1 #<procedure:even?> '(1 1 2))
    ;; >(or-map/v1 #<procedure:even?> '(1 2))
    ;; >(or-map/v1 #<procedure:even?> '(2))
    ;; <#t
    ;; #t
    
    (or-map/v2 even? '(1 1 1 2))
    ;; >(or-map/v2 #<procedure:even?> '(1 1 1 2))
    ;; >(or-map/v2 #<procedure:even?> '(1 1 2))
    ;; >(or-map/v2 #<procedure:even?> '(1 2))
    ;; >(or-map/v2 #<procedure:even?> '(2))
    ;; <#t
    ;; #t
    

    【讨论】:

      【解决方案2】:

      andor 在尾部位置计算它们的最后一个表达式。这是由 Scheme 标准保证的;参见,例如,http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-14.html#node_sec_11.20

      另一方面,nor 必须否定or 的结果。根据定义,这意味着or 的结果不会在尾部位置进行评估,因为它必须在返回给调用者之前传递给not

      【讨论】:

        【解决方案3】:

        你在问:

        正确地假设我对 or-map 的两个定义在性能方面是等效的,还是一个比另一个更可取?

        但请注意,您的第一个函数 等同于第二个函数(不会产生相同的结果)。如果你打电话给他们,你可以验证这一点:

        (or-map (lambda (x) (member x '(1 2 3))) '(1 2 a))
        

        原因是在第一个函数中,当(proc (car lst)) 返回的值与#false 不同时,您返回#true,但该函数应该返回(proc (car lst)) 的值。所以“正确”的版本(即相当于 Racket ormap 的版本)只是第二个。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-29
          • 2012-09-11
          • 1970-01-01
          • 2011-12-29
          • 2013-02-01
          相关资源
          最近更新 更多