>
>
> (defun find-all (item sequence &rest keyword-args
> &key (test #'eql) test-not &allow-other-keys)
> "Find all those elements of sequence that match item,
> according to the keywords. Doesn't alter sequence."
> (if test-not
> (apply #'remove item sequence
> :test-not (complement test-not) keyword-args)
> (apply #'remove item sequence
> :test (complement test) keyword-args)))
>
Both Clojure and Common Lisp have a function to remove a value from a sequence. However, the CL version supports quite a few more options, which accounts for the complexity of this FIND-ALL function.
The function 'remove' in both languages obviously has to take an item to look for and a sequence to search. But the CL version also accepts various keyword arguments that tailor how it behaves. There is a default test which determines whether the item matches a given element, but another test can be specified by the :test keyword. This allows for various flavors of equality when matching. There is also a :test-not keyword which works in the opposite sense. You can see both of those keywords above. The CL version also accepts other keywords such as :start, :end, :from-end, :count, and :key.
In the section after the lambda-list keyword &key above you can see that FIND-ALL also accepts :test and :test-not keywords, with a default :test of the function EQL. The lambda-list keyword &allow-other-keys is used to show that FIND-ALL will also accept the other REMOVE keywords and pass them along.
Before the &key section, however, there is the &rest section. What actually happens is that every argument passed to FIND-ALL, besides the first two required arguments, is captured as a list in the variable KEYWORD-ARGS. CL also processes the :test and :test-not keyword arguments separately, but they and their keywords are also bundled up in KEYWORD-ARGS. This makes it easy to pass everything along to REMOVE via the APPLY form.
Here is a Clojure implementation that is pretty true to the spirit of the CL version. All of the optional arguments are captured in the list 'keyword-args', but we also break out the keywords into a map:
(defn find-all [item coll & keyword-args]
(let [keywords (apply hash-map keyword-args)]
(if (:test-not keywords)
(remove (fn [elt] ((:test-not keywords) item elt)) coll)
(remove (fn [elt] (not ((:test keywords) item elt))) coll))))
Since we want to find things that match our criterion we reverse the sense of the :test and remove things that fail our criterion. But we have to do the opposite with the :test-not case here since Clojure's 'remove' doesn't understand that :test-not is the opposite of :test. Notice also that we can call 'remove' directly (without 'apply') since we aren't passing in a list of keywords.
(find-all 1 '(1 2 3 2 1) :test =) => (1 1)
(find-all 1 '(1 2 3 2 1) :test not=) => (2 3 2)
(find-all 1 '(1 2 3 2 1) :test-not =) => (2 3 2)
Have all good days,
David Sletten
That's really nice Meikel. I was trying to remember how to do the new keywords, but I couldn't find an example. You have the default value for :test in there too. Interesting idea to replace 'remove not' with 'filter'.
> I was trying to remember how to do the new keywords, but I couldn't find an example. You have the default value for :test in there too.
It's basically normal map destructuring in the varags position, ie. after the & in the argument list.
Sincerely
Meikel