'Would like to understand why the SOME function in examples below are returning inconsistent results

I'm new to common lisp, and I am using SBCL (version 2.1.5) in linux, I am learning about the SOME applicative operator, and I ran these 2 examples that seem to me to be giving inconsistent results:

  • (some #'= '(1 2 3 4 5) '(5 4 3 2 1)) T

  • (some #'= '(1 2 3 4) '(4 3 2 1)) NIL

The first invocation makes sense to me, but the second invocation seems to be inconsistent. Why wouldn't the second invocation also return 'T' like the first invocation?

Thanks for any help understanding this.



Solution 1:[1]

(some #'= '(1 2 3 4) '(4 3 2 1))

means

(or (= 1 4)
    (= 2 3)
    (= 3 2)
    (= 4 1))

That is: the function = is applied to the first elements of the given sequences, then to the second elements and so on. It finishes as soon as one of the sequences is exhausted.

If you want to have the set intersection between two lists (the :test is a bit redundant here, because the default test eql also compares numbers):

(intersection '(1 2 3 4) '(4 3 2 1) :test #'=)

This returns the entire intersection. If you only need to test whether there is any common member, finishing as soon as one is found, you could do it like this:

(find-if (lambda (e)
           (member e '(4 3 2 1)))
         '(1 2 3 4))

or

(loop :for e :in '(1 2 3 4)
      :thereis (member e '(4 3 2 1)))

Solution 2:[2]

Let's look at a simpler SOME example:

(SOME #'EVENP '(1 2 3 4 5))

It is simpler because there is only one sequence of values being iterated. As a consequence, the predicate is expected to be given a single argument. The result here is T because some elements in the input lists satisfy EVENP.

When you have more than one sequence, they are iterated in parallel: the first elements of each sequence are used together as arguments to the predicate. Then all the second elements are used as arguments to the second call to the predicate, etc. until at least one sequence is empty.

For example, let's define a function divisible-by that takes two arguments:

USER> (defun divisible-by (value divisor)
        (= 0 (mod value divisor)))
DIVISIBLE-BY

You could say, for example (DIVISIBLE-BY 10 5) and it would return T.

Let's also TRACE it (the actual trace output is implementation dependent, here I'm using SBCL).

USER> (trace divisible-by)
(DIVISIBLE-BY)

Let's use it in a call to SOME, with 2 lists (any other amount of sequences is an error):

USER> (some #'divisible-by '(10 20 30) '(3 7 6))
  0: (DIVISIBLE-BY 10 3)
  0: DIVISIBLE-BY returned NIL
  0: (DIVISIBLE-BY 20 7)
  0: DIVISIBLE-BY returned NIL
  0: (DIVISIBLE-BY 30 6)
  0: DIVISIBLE-BY returned T
T

You can see that each pair of elements from both lists are used as arguments to the successive calls to divisible-by.

The same logic applies to other higher-order functions in Common Lisp:

USER> (mapcar #'list '(0 1 2) '(a b c) '(x y z))
((0 A X) (1 B Y) (2 C Z))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Svante
Solution 2 coredump