Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: Idiomatic way to call contains? on a lazy sequence

Is there an idiomatic way of determining if a LazySeq contains an element? As of Clojure 1.5 calling contains? throws an IllegalArgumentException:

IllegalArgumentException contains? not supported on type: clojure.lang.LazySeq      
clojure.lang.RT.contains (RT.java:724)

Before 1.5, as far as I know, it always returned false.

I know that calling contains? on a LazySeq may never return as it can be infinite. But what if I know it isn't and don't care if it is evaluated eagerly?

What I came up with is:

(defn lazy-contains? [col key]
  (not (empty? (filter #(= key %) col))))

But it doesn't feel quite right. Is there a better way?

like image 676
nansen Avatar asked Apr 28 '13 16:04

nansen


2 Answers

First, lazy seqs are not efficient for checking membership. Consider using a set instead of a lazy seq.

If a set is impractical, your solution isn't bad. A couple of possible improvements:

  1. "Not empty" is a bit awkward. Just using seq is enough to get a nil-or-truthy value that your users can use in an if.You can wrap that in boolean if you want true or false.

  2. Since you only care about the first match, you can use some instead of filter and seq.

  3. A convenient way to write an equality predicate is with a literal set, like #{key}, though if key is nil this will always return nil whether nil is found our not.

All together that gives you:

(defn lazy-contains? [col key]
  (some #{key} col))
like image 83
Chouser Avatar answered Oct 23 '22 02:10

Chouser


If you use some instead of filter as in your example, you'll get an immediate return as soon as a value is found instead of forcing evaluation of the entire sequence.

(defn lazy-contains? [coll key]
  (boolean (some #(= % key) coll)))

Edit: If you don't coerce the result to a boolean, note that you'll get nil instead of false if the key isn't found.

like image 5
dfreeman Avatar answered Oct 23 '22 01:10

dfreeman