为什么 apply map and to list of expressions returns 只有一个布尔值?

why does apply map and to list of expressions returns only one boolean?

(define (cart . lists)
  (cond ((null? lists) '())
        ((= (length lists) 1) (map list (first lists)))
        (else
         (append-map (lambda(x)
                       (map (lambda(y) (cons y x))
                            (first lists)))
                     (apply cart (rest lists))))))

(define (numbers n)
   (define (reversed-numbers n)
     (if (= n 0)
         '()
         `(,n . ,(reversed-numbers (- n 1)))))
   (reverse (reversed-numbers n)))

(define (gen-truth n vals)
       (apply cart (map (lambda(x) list vals) (numbers n))))

(gen-truth 2 '(#t #f))  returns: '((#t #t) (#f #t) (#t #f) (#f #f))

(define and-l (lambda x 
    (if (null? x)
        #t
        (if (car x) (apply and-l (cdr x)) #f))))

为什么:(apply map and-l (gen-truth 2 '(#t #f)))

return '(#f #f) ?我希望它 return 每个包含布尔值对的子表达式的布尔值。


更好的and-l程序


问题是 and-l 过程以一种可能未预料到的方式运行。在这里,x 在传递到过程主体之前被放入列表中。考虑:

scratch.rkt> (define bad-proc (lambda x x))
scratch.rkt> (bad-proc '(#t #f))
'((#t #f))

人们可能希望 and-l 过程测试一个列表是否包含所有真值,而传递给过程主体的那个确实:

scratch.rkt> (and-l-bad '(#t #f))
#t
scratch.rkt> (and-l-bad '(#f #f))
#t
scratch.rkt> (and-l-bad '(#t #t))
#t

这是 and-l 的正确版本;请注意,不需要 apply:

(define and-l
  (lambda (x)
    (if (null? x)
        #t
        (and (car x) (and-l (cdr x))))))

正在测试新程序:

scratch.rkt> (and-l '(#t #f))
#f
scratch.rkt> (and-l '(#f #f))
#f
scratch.rkt> (and-l '(#t #t))
#t

现在 and-l 正在正常工作,我们将注意力转向:

(apply map and-l (gen-truth 2 '(#t #f)))

同样,这里不需要 apply。在这里做 (apply map and-l ;....) 甚至没有意义。 apply 过程将过程和列表作为参数,并使用列表的元素作为过程的参数。所以,(apply + '(1 2 3 4)) 等价于 (+ 1 2 3 4)。当前上下文中不需要此功能;所需要的只是 mapand-l 应用于 gen-truth:

返回的列表中的每个布尔值列表
scratch.rkt> (gen-truth 2 '(#t #f))
'((#t #t) (#f #t) (#t #f) (#f #f))
scratch.rkt> (map and-l (gen-truth 2 '(#t #f)))
'(#t #f #f #f)

挽救原始and-l程序


请注意,and-l-bad 不适用于参数列表,但适用于包装在列表中并传递给过程主体的未指定数量的参数:

scratch.rkt> (and-l-bad #t #f #f)
#f
scratch.rkt> (and-l-bad #t #t #t)
#t
scratch.rkt> (and-l-bad #f #f #t)
#f

考虑到这一点,无需像上面那样将 and-l-bad 重写为 and-l 即可实现 OP 目标。相反,重新考虑 (apply map and-l (gen-truth 2 '(#t #f))):

scratch.rkt> (map (lambda (x) (apply and-l-bad x)) (gen-truth 2 '(#t #f)))
'(#t #f #f #f)

此处,map 将函数应用于 gen-truth 返回的列表中的每个布尔列表。该函数采用布尔列表并将 applyand-l-bad 应用于它,例如(apply and-l-bad '(#t #f) --> (and-l-bad #t #f),这正是 and-l-bad 所期望的。


and-proc


过程 and-l-badand 非常相似,因为它采用未指定数量的参数和 returns 参数与 and 组合的结果:

scratch.rkt> (and-l-bad #t #t #f #t)
#f
scratch.rkt> (and #t #t #f #t)
#f

但有一个重要的区别:and-l-bad是一个过程,而and是一个特殊形式.过程总是对其参数的 all 求值,但特殊形式有特殊的求值规则。特殊形式 and 按顺序计算其参数,直到结果已知,返回该序列中遇到的第一个 #f 或序列的最后一个值。这是短路评估

and-l-bad 更好的名称可能是 and-proc。原名 and-l 暗示 and 应用于列表(这不是真的),而 and-proc 强调这是一个 过程 其行为类似于 and.

为什么你首先需要像 and-proc 这样的东西?好吧,apply 为其第一个参数采用一个过程,因此 (map (lambda (x) (apply and x)) (gen-truth 2 '(#t #f))) 将不起作用。但这会起作用:

scratch.rkt> (map (lambda (x) (apply and-proc x)) (gen-truth 2 '(#t #f)))
'(#t #f #f #f)

这里我们使用了类似 and 过程 ,其中 and 本身不起作用。 and-proc 的缺点是它总是评估其参数的 all;这并不总是可取的。


如何避免编写类似 and 的新过程


在这里完全可以避免使用 and 编写新程序来处理:

scratch.rkt> (map (lambda (x) (andmap identity x)) (gen-truth 2 '(#t #f)))
'(#t #f #f #f)

这里 (lambda (x) (andmap identity x)) 是一个过程,它接受它的参数并将它们与 and 结合起来。也就是说,((lambda (x) (andmap identity x)) '(#t #f)) 等同于 (and (identity #t) (identity #f))。此过程像以前一样映射到布尔列表列表上。

这等同于此答案顶部定义的 and-l,而 and-l 实际上可以更好地定义为:

(define (and-l xs)
  (andmap identity xs))