How to write this in Clojure? Example from PAIP

50 views
Skip to first unread message

nickikt

unread,
Sep 29, 2010, 3:55:06 AM9/29/10
to Clojure
Hallo all,

I'm started working on PAIP and doing the examples in Clojure. I
understand what this function does (i think) now but I'm not sure how
to write it in Clojure since I have no CL background.

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

So I just wanted to drop it in here. Can you make a idiomatic clojure
version?

David Sletten

unread,
Sep 29, 2010, 9:44:14 AM9/29/10
to clo...@googlegroups.com

On Sep 29, 2010, at 3:55 AM, nickikt wrote:

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


Meikel Brandmeyer

unread,
Sep 29, 2010, 11:01:12 AM9/29/10
to Clojure
Hi,

a slight enhancement for 1.2: Clojure now supports keyword arguments
directly.

(defn find-all
[item coll & {:keys [test test-not] :or {test =}}]
(if test-not
(remove #(test-not item %) coll)
(filter #(test item %) coll)))

Sincerely
Meikel

nickikt

unread,
Sep 29, 2010, 2:23:27 PM9/29/10
to Clojure
Thx, for your answers. Helps alot.

I think the clojure version is cleaner. The meaning of all those &...
words are confusing in CL.

David Sletten

unread,
Sep 29, 2010, 3:37:17 PM9/29/10
to clo...@googlegroups.com

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'.

Meikel Brandmeyer

unread,
Sep 29, 2010, 4:06:52 PM9/29/10
to clo...@googlegroups.com
Hi,

Am 29.09.2010 um 21:37 schrieb David Sletten:

> 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

Reply all
Reply to author
Forward
0 new messages