Alternatives to contains?

1,942 views
Skip to first unread message

Paul Mooser

unread,
Jan 29, 2009, 2:06:43 PM1/29/09
to Clojure
I know this has been discussed on the list before to some extent, but
does clojure by default have any operations which actually do what
"contains?" sounds like it would do, for all collections? I know that
you can easily write something similar using "some", but often times
you just want to find if something is in a list of items.

I've seen Rich say that contains? is for associative things, but I
think it is an unfortunate name - if the idea is to express that the
collection has a value for that key, I think the name would ideally
express that, like:

contains-key?
has-key?
maps?

I see that clojure-contrib has includes?, which perhaps indicates that
clojure by default doesn't have such a function.

Cosmin Stejerean

unread,
Jan 29, 2009, 2:55:16 PM1/29/09
to clo...@googlegroups.com
On Thu, Jan 29, 2009 at 1:06 PM, Paul Mooser <taro...@gmail.com> wrote:

I know this has been discussed on the list before to some extent, but
does clojure by default have any operations which actually do what
"contains?" sounds like it would do, for all collections? I know that
you can easily write something similar using "some", but often times
you just want to find if something is in a list of items.

I've seen Rich say that contains? is for associative things, but I
think it is an unfortunate name - if the idea is to express that the
collection has a value for that key, I think the name would ideally
express that, like:

contains-key?
has-key?
maps?

I would prefer has-key? for checking if a key is in a map and contains? for checking if an element is in a collection.

--
Cosmin Stejerean
http://offbytwo.com

Dan Larkin

unread,
Jan 29, 2009, 4:56:14 PM1/29/09
to clo...@googlegroups.com
What about leaving "contains?" as is and adding "in?" which would work like "in" in python.

(contains? [1 2 50] 50) => false
(in? [1 2 50] 50) => true


Cosmin Stejerean

unread,
Jan 29, 2009, 5:23:27 PM1/29/09
to clo...@googlegroups.com
If in? was to be added how would it behave when given a map as the first argument? I would rather have "contains?" do the right thing for list/vectors/sets and keep its current behavior for maps. If we do actually need a function like contains that ONLY accepts a map as the first argument I think a name like has-key? is the most intuitive.

Dan Larkin

unread,
Jan 29, 2009, 5:32:46 PM1/29/09
to clo...@googlegroups.com

On Jan 29, 2009, at 5:23 PM, Cosmin Stejerean wrote:
>
> If in? was to be added how would it behave when given a map as the
> first argument? I would rather have "contains?" do the right thing
> for list/vectors/sets and keep its current behavior for maps. If we
> do actually need a function like contains that ONLY accepts a map as
> the first argument I think a name like has-key? is the most intuitive.
>


I think "in?" would behave like "contains?" when given a map:

(in? {:a 1 :b 2 :c 3} :a) => true
(in? {:a 1 :b 2 :c 3} :d) => false


Chouser

unread,
Jan 29, 2009, 5:48:55 PM1/29/09
to clo...@googlegroups.com

I thought we had beaten this one entirely to death:
http://groups.google.com/group/clojure/browse_thread/thread/ff224d2b88b671e7/575cefc2c03ce154

And yet it lives!

What is the drawback of the (some #{:y} [:x :y :z]) idiom? Is it too
verbose? Too slow? Too flexible? Too good a re-use of existing
functionality? Too helpful in opening ones eyes to the possibilities
of sets and higher order functions?

And if you really don't want to use it (why again?) there is
clojure.contrib.seq-utils/includes?, so why not use that?

--Chouser

Mark Volkmann

unread,
Jan 29, 2009, 5:52:41 PM1/29/09
to clo...@googlegroups.com
On Thu, Jan 29, 2009 at 4:48 PM, Chouser <cho...@gmail.com> wrote:
>
> On Thu, Jan 29, 2009 at 5:32 PM, Dan Larkin <d...@danlarkin.org> wrote:
>>
>> On Jan 29, 2009, at 5:23 PM, Cosmin Stejerean wrote:
>>>
>>> If in? was to be added how would it behave when given a map as the
>>> first argument? I would rather have "contains?" do the right thing
>>> for list/vectors/sets and keep its current behavior for maps. If we
>>> do actually need a function like contains that ONLY accepts a map as
>>> the first argument I think a name like has-key? is the most intuitive.
>>
>> I think "in?" would behave like "contains?" when given a map:
>>
>> (in? {:a 1 :b 2 :c 3} :a) => true
>> (in? {:a 1 :b 2 :c 3} :d) => false
>
> I thought we had beaten this one entirely to death:
> http://groups.google.com/group/clojure/browse_thread/thread/ff224d2b88b671e7/575cefc2c03ce154
>
> And yet it lives!
>
> What is the drawback of the (some #{:y} [:x :y :z]) idiom? Is it too
> verbose? Too slow? Too flexible? Too good a re-use of existing
> functionality? Too helpful in opening ones eyes to the possibilities
> of sets and higher order functions?

I vote for too verbose. ;-)

> And if you really don't want to use it (why again?) there is
> clojure.contrib.seq-utils/includes?, so why not use that?

I'd like for that to be moved to core so I don't have to load it ...
which is also verbose for something that is commonly needed.

--
R. Mark Volkmann
Object Computing, Inc.

Mark Engelberg

unread,
Jan 29, 2009, 6:03:13 PM1/29/09
to clo...@googlegroups.com
On Thu, Jan 29, 2009 at 2:52 PM, Mark Volkmann
<r.mark....@gmail.com> wrote:
>> What is the drawback of the (some #{:y} [:x :y :z]) idiom? Is it too
>> verbose? Too slow? Too flexible? Too good a re-use of existing
>> functionality? Too helpful in opening ones eyes to the possibilities
>> of sets and higher order functions?
>
> I vote for too verbose. ;-)

(some #{:y] [:x :y :z]) is fewer characters than
(includes? :y [:x :y :z]) so your vote of "too verbose" is hard to justify.

Mark Volkmann

unread,
Jan 29, 2009, 6:13:04 PM1/29/09
to clo...@googlegroups.com

Good point. I was thinking more in terms of number of "noise"
characters instead of number of total characters. Looking at it that
way it's
(${}[])
versus
([])

The current form requires you to think a little more. You're thinking
"I want to see if this thing is one of several possible values.
So I want to compare one thing to a set of things.
How do I do that?
Oh, I have to put the one thing in a set and the set of things in a vector."

That's easy to remember if you code in Clojure daily, but not so easy
if you only code in Clojure once every two weeks and code in Java or
something else the rest of the time.

Message has been deleted

Paul Mooser

unread,
Jan 29, 2009, 6:18:42 PM1/29/09
to Clojure
Well, sorry for bringing it up again, but the reason I did so is I
find the name uncommonly confusing, especially considering how good
the names in clojure are in general. I know not everything can be
clear without reading the documentation, but it was only clear to me
that contains? was not what I wanted because the docs explicitly
stated that it would never search linearly for a value.

I have no problem with higher order functions (obviously), but to me a
recurring theme in functional programming is separating structural
concerns from the code itself. I think it's such a common task to want
to know if a collection contains something that it is very sensible to
provide a function which perform that task for every collection - when
something happens all the time, in almost any program of significant
size, it is often worth giving that something a name.

I realize that Rich has concerns (from reading previous threads on
this subject) about having functions which have widely varying
performance (from constant to linear time) depending on the sort of
collection, but to me it seems very natural to have the performance of
a lookup operation depend on the kind of collection you're looking in.
I would not expect any method that operated on lists to have anything
but linear time, and similarly I'd expect hash table lookups to
provide operations in constant time (or "constant time", with
clojure's maps).

Obviously code like (some #{x} coll) works, but creating a set to use
as a way to search a list to find if it contains an item doesn't
scream "clear" to me - I suppose idioms aren't always chosen for
clarity of expression, or perhaps I'm merely not one of the
initiated, yet.

On Jan 29, 2:48 pm, Chouser <chou...@gmail.com> wrote:
> I thought we had beaten this one entirely to death:http://groups.google.com/group/clojure/browse_thread/thread/ff224d2b8...

Achim Passen

unread,
Jan 29, 2009, 6:23:57 PM1/29/09
to clo...@googlegroups.com
Hi!

Am 29.01.2009 um 23:52 schrieb Mark Volkmann:

I'd like for that to be moved to core so I don't have to load it ...
which is also verbose for something that is commonly needed.

"includes?" being commonly needed might indicate that people really should be using sets instead of lists or vectors in these cases. Sets are the only data structures that provide efficient contains?(-value), plus contains? works as expected with sets.

My guess is that the omission of includes? in core is not an oversight after all, but an incentive to pick the right data structure for the task at hand.

Kind regards,
achim

Paul Mooser

unread,
Jan 29, 2009, 6:27:52 PM1/29/09
to Clojure
Additionally, I initially found (some #{:y} [:x :y :z]) to be
confusing. The documentation of "some" (and or, in turn) uses the
phrase "logical true" - I initially assumed this to mean actually
true, but I suppose what it means is "not false or nil".

In that case, it makes me think of the degenerate example (I realize
this is slightly stupid):

(some #{false} (list false))

On Jan 29, 2:48 pm, Chouser <chou...@gmail.com> wrote:
> What is the drawback of the (some #{:y} [:x :y :z]) idiom?  Is it too
> verbose?  Too slow?  Too flexible?  Too good a re-use of existing
> functionality?  Too helpful in opening ones eyes to the possibilities
> of sets and higher order functions?
> --Chouser

Paul Mooser

unread,
Jan 29, 2009, 6:32:46 PM1/29/09
to Clojure
I understand that sets or hash maps are more efficient structures for
lookups. However, in certain cases (especially if I'm programming
something interactively), I have either short lists or data which is
naturally a list that will be faster to walk through than to convert
it to a set (which implicitly assumes I'll walk through it). Anyway,
point taken, but I'm not sure that completely addresses the question
I'm posing.

Jason Wolfe

unread,
Jan 29, 2009, 6:38:13 PM1/29/09
to Clojure
> In that case, it makes me think of the degenerate example (I realize
> this is slightly stupid):
>
> (some #{false} (list false))

Maybe this is an argument for adding "any?" to the list of core
functions?

-Jason

Kevin Downey

unread,
Jan 29, 2009, 6:55:49 PM1/29/09
to clo...@googlegroups.com
actually rhickey showed up on irc and pointed something out:

 15:23   rhickey : user=> (.contains [1 2 3] 2)
 15:23   rhickey : true
 15:23   rhickey : user=> (.contains '(1 2 3) 2)
 15:23   rhickey : true
 15:23   rhickey : what contains debate? :)


so because seqs, vectors, etc are java collections

user=> (.contains (map #(.getName %) (.getMethods (class (seq [:a])))) "contains")
true

http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html
--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

Jason Wolfe

unread,
Jan 29, 2009, 7:01:27 PM1/29/09
to clo...@googlegroups.com
except (.contains nil 2) ==> NPE

:-)

-Jason

Paul Mooser

unread,
Jan 29, 2009, 7:01:56 PM1/29/09
to Clojure
Good point - I actually looked in Collections, but failed to look in
Collection itself.

On Jan 29, 3:55 pm, Kevin Downey <redc...@gmail.com> wrote:
> actually rhickey showed up on irc and pointed something out:
> ...

Stuart Sierra

unread,
Jan 29, 2009, 9:36:35 PM1/29/09
to Clojure
On Jan 29, 5:48 pm, Chouser <chou...@gmail.com> wrote:
> What is the drawback of the (some #{:y} [:x :y :z]) idiom?  

Too hard to understand at first, which is why I wrote seq-utils/
includes?.

But a note to all: I've hardly used includes? since I wrote it. I
found that if I was testing for the presence/absence of elements in a
collection, then I was usually better off using a set in the first
place.

Most languages don't include sets as standard types, so we don't think
of them. But whenever you have a collection in which order doesn't
matter, a set is often better than a vector or list.

-Stuart Sierra

Paul Barry

unread,
Jan 29, 2009, 10:41:21 PM1/29/09
to clo...@googlegroups.com
This is actually the point of this whole debate.  You would assume that contains? works like java.util.Collection.contains, but instead it means something completely semantically different.

user=> (.contains [4 5 6] 4)
true
user=> (contains? [4 5 6] 4)
false

Even worse, you do this and think it works:

user=> (.contains [1 2 3] 1)
true
user=> (contains? [1 2 3] 1)
true

This isn't about having a method that does the same thing as .contains or includes? from contrib, it's about naming this method, the one that tells you if the associative thing has a particular key, something less confusing.

This is just one of those things that a lot of people new to Clojure will learn the hard way at some point.  And they'll probably send another email to the mailing list and we'll have the debate all over again.  Once Clojure goes 1.0, this problem is permanent, so we have until then to convince Rich to rename this method to contains-key? (which mimics the name in the java.util.Map interface) or has-key? (for something shorter).

Rich Hickey

unread,
Jan 29, 2009, 11:46:00 PM1/29/09
to Clojure


On Jan 29, 10:41 pm, Paul Barry <pauljbar...@gmail.com> wrote:
> This is actually the point of this whole debate. You would assume that
> contains? works like java.util.Collection.contains, but instead it means
> something completely semantically different.
> user=> (.contains [4 5 6] 4)
> true
> user=> (contains? [4 5 6] 4)
> false
>
> Even worse, you do this and think it works:
>
> user=> (.contains [1 2 3] 1)
> true
> user=> (contains? [1 2 3] 1)
> true
>
> This isn't about having a method that does the same thing as .contains or
> includes? from contrib, it's about naming this method, the one that tells
> you if the associative thing has a particular key, something less confusing.
>
> This is just one of those things that a lot of people new to Clojure will
> learn the hard way at some point. And they'll probably send another email
> to the mailing list and we'll have the debate all over again. Once Clojure
> goes 1.0, this problem is permanent, so we have until then to convince Rich
> to rename this method to contains-key? (which mimics the name in the
> java.util.Map interface) or has-key? (for something shorter).
>
> Maybe I'll setuphttp://pleaserenamecontainsrich.com:)
>

Sequential lookup is not an important operation. Clojure includes sets
and maps, and if you are going to be looking things up you should be
using them. contains? maps to java.util.Set.contains. The fact that
java.util.Collection also has contains is, IMO, a mistake, as you
really can't write code to an interface with dramatically varying
performance characteristics.

So, lookups in sets and maps take priority and get the best name -
contains?

People write naive programs that do linear lookups with contains() in
other languages, and get correspondingly bad n-squared performance -
that's not an argument for encouraging that in Clojure. If they get an
explanation of why it's a bad idea, and how to use sets and maps,
that's fine.

If you want a linear lookup function, it's going to have a big warning-
sign name, like linear-lookup.

I'm not persuaded in any way to rename contains?

Rich

Mark Volkmann

unread,
Jan 30, 2009, 6:46:10 AM1/30/09
to clo...@googlegroups.com

Embarassingly, I hadn't even considered that option. I'm happy with
the following.

user=> (def s #{"a" "c" "d" "m"})
#'user/s
user=> (contains? s "d")
true
user=> (contains? s "e")
false

You're right. I was using a vector where I should have used a set.

Reply all
Reply to author
Forward
0 new messages