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

笑着哭i 提交于 2021-01-29 05:45:50

问题


(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))))

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

return '(#f #f) ? i would expect it return a Boolean for each of the sub expressions containing pairs of Boolean values.


回答1:



A Better and-l Procedure


The problem is that the and-l procedure is operating in a way that probably was not anticipated. Here, x is put into a list before being passed along to the body of the procedure. Consider:

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

One would probably expect the and-l procedure to test whether a list contains all true values, and the one passed to the body of the procedure does:

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

Here is a correct version of and-l; note that there is no need for apply:

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

Testing the new procedure:

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

Now that and-l is working as it should, we turn our attention to:

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

Again, there is no need for apply here. It does not even make sense here to do (apply map and-l ;....). The apply procedure takes a procedure and a list as arguments, and uses the elements of the list as arguments to the procedure. So, (apply + '(1 2 3 4)) is equivalent to (+ 1 2 3 4). This capability is not needed in the current context; all that is needed is map to apply and-l to each list of booleans in the list returned by 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)

Salvaging the Original and-l Procedure


Note that and-l-bad works not on a list of arguments, but on an unspecified number of arguments which are wrapped in a list and passed to the procedure body:

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

With this in mind, OP goal can be achieved without rewriting and-l-bad to and-l as above. Instead, rethink (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)

Here, map applies a function to each boolean list in the list returned by gen-truth. That function takes a boolean list and applies apply with and-l-bad to it, so that,e.g. (apply and-l-bad '(#t #f) --> (and-l-bad #t #f), which is exactly what and-l-bad is expecting.


and-proc


The procedure and-l-bad is really like and, in that it takes an unspecified number of arguments and returns the result of combining the arguments with and:

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

But there is an important difference: and-l-bad is a procedure, while and is a special form. Procedures always evaluate all of their arguments, but special forms have special evaluation rules. The special form and evaluates its arguments sequentially until the result is known, either returning the first #f encountered in that sequence, or the last value of the sequence. This is short-circuit evaluation.

A better name for and-l-bad would perhaps be and-proc. The original name, and-l suggested that and was applied to a list (which was not true), while and-proc emphasizes that this is a procedure which behaves similarly to and.

Why would you need something like and-proc in the first place? Well, apply takes a procedure for its first argument, so (map (lambda (x) (apply and x)) (gen-truth 2 '(#t #f))) will not work. But this will work:

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

Here we have used an and-like procedure where and itself would not work. The downside of and-proc is that it always evaluates all of its arguments; this is not always desirable.


How to Avoid Writing a New and-like Procedure


Writing a new procedure to handle using and here could have been avoided altogether:

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

Here (lambda (x) (andmap identity x)) is a procedure that takes its arguments and combines them with and. That is, ((lambda (x) (andmap identity x)) '(#t #f)) is equivalent to (and (identity #t) (identity #f)). This procedure is mapped over the list of boolean lists as before.

This is equivalent to the and-l defined at the top of this answer, and and-l could in fact be better-defined as:

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


来源:https://stackoverflow.com/questions/61334629/why-does-apply-map-and-to-list-of-expressions-returns-only-one-boolean

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!