为啥 Scheme `filter` 表单不能“按顺序”处理列表元素?

Posted

技术标签:

【中文标题】为啥 Scheme `filter` 表单不能“按顺序”处理列表元素?【英文标题】:Why might the Scheme `filter` form not process list elements 'in order'?为什么 Scheme `filter` 表单不能“按顺序”处理列表元素? 【发布时间】:2022-01-09 19:56:45 【问题描述】:

(filter procedure list)procedure 应用于 list 的每个元素 并返回一个仅包含 procedure 的元素的新列表 返回真。 (R. Kent Dybvig The Scheme Programming Language) (online)

从这个描述中可能不明显的是,虽然返回的元素 list 出现的顺序与list 相同,procedure 的调用顺序不是 在 R6RS 中指定。 (然而,Racket 将“从第一个到最后一个元素”应用到每个元素)

最近活跃的answer 提到它需要一个 filterfunc 来处理它的参数列表 按顺序。这个函数应该怎么写?

提供了我对问题的解释的答案。

【问题讨论】:

【参考方案1】:

Scheme 实现可能使用什么顺序,为什么?让我们尝试一下(所有代码都在 Chez Scheme REPL 中运行):

    应用程序的显示顺序
> (filter (lambda (x) (display x) (even? x))
    '(0 1 2 3 4 5)))
452301(0 2 4)
> 
    为什么是这个订单? R6RS 实现必须检查 list正确的列表
以空列表结束:
> (filter (lambda (x) (display x) (even? x))
    '(0 1 2 . 3)))
Exception in filter: (0 1 2 . 3) is not a proper list
> 
没有循环性:
> (define xs '(0 1 2 3))
> (set-cdr! (cdddr xs) (cdr xs))
> (filter (lambda (x) (display x) (even? x)) xs)
Exception in filter: (0 1 2 3 1 2 ...) is not a proper list
> 
在检查这些要求的 Chez Scheme 中实现filter 在过滤时,导致谓词应用程序的“452301”顺序, 可以看到here (第 589-617 行:下面包含一个版本作为剧透,作为滚动浏览的替代方法 github上的源码)
    What? Chez Scheme 代码使用"tortoise and hare" algorithm 检测周期。如果没有错误检查,代码可能是:
> (let ([filter
    (lambda (pred? ls)
      (let f ([fast ls])
        (if (pair? fast)
          (let ([rest (f (cdr fast))])
            (if (pred? (car fast))
              (cons (car fast) rest)
              rest))
          '())))])
  
    (filter (lambda (x) (display x) (even? x))
      '(0 1 2 3 4 5)))
543210(0 2 4)
> 

(标识符fast用于匹配Chez Scheme代码:否则可能是ls

    如何将其更改为“从头到尾”调用 pred?
rest变量替换为累加器(将以下内容与上面的3进行比较; 变化很小,但过滤后的元素是consed to acc, 所以它必须颠倒才能给出结果):
> (let ([filter
    (lambda (pred? ls)
      (let f ([fast ls] [acc '()])
        (if (pair? fast)
          (f (cdr fast)
             (if (pred? (car fast))
               (cons (car fast) acc)
               acc))
          (reverse acc))))])
  
    (filter (lambda (x) (display x) (even? x))
      '(0 1 2 3 4 5)))
012345(0 2 4)
> 

所以 4 可以用作所需的filterfunc。这将是一个有趣的练习 添加错误检查,例如 Chez Scheme 实现,这实际上是:

(define (filter pred? ls) (unless (procedure? pred?) (error #f "not a procedure" pred?)) (or (let f ((pred? pred?) (fast ls) (slow ls)) (if (pair? fast) (let ((fast1 (cdr fast))) (if (pair? fast1) (and (not (eq? fast1 slow)) (let ((fast2 (cdr fast1))) (let ((rest (f pred? fast2 (cdr slow)))) (and rest (if (pred? (car fast)) (if (pred? (car fast1)) (if (eq? rest fast2) fast (list* (car fast)@ 987654353@ (cons (car fast) rest)) (if (pred? (car fast1)) (if (eq? rest fast2) fast1 (cons (car fast1) rest)) rest)))))) (and (null? fast1) (if (pred? (car fast)) fast '())))) (and (null? fast) '()))) (error #f "circular/improper list" ls)))

【讨论】:

这是一个很好的答案。【参考方案2】:

它涉及到你不应该在过程中使用一些外部变量的突变,并且它假设实现可以应用并行性,例如 map-reduce。

【讨论】:

以上是关于为啥 Scheme `filter` 表单不能“按顺序”处理列表元素?的主要内容,如果未能解决你的问题,请参考以下文章

为啥让首选在Scheme中定义?

为啥模态演示表单未按预期显示?

在html中的表单,为啥一按回车键就提交表单了呢?

为啥 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?

为啥 miniKanren 中的“disj”在 Scheme 中有效,而在 Racket 中无效?

Guava:为啥没有 Lists.filter() 函数?