0

I am a newbie for clojure and a little bit confused with the use of contains? form to different data structures.

The result of applying contains to a vector, set or a map is just what I expect, it tests if a key (or index) is present in the collection. But when it comes to a list, such as


(def li '(1 2 3)) ; define a list
(contains? li 1) ; returns false !!??

I know it is NOT that straightforward to understand how contains? works with a list, since its document says the implementation operates in a constant or log time. So here it doesnt really make sense to test in less than log time if 1 is in a list or its index range. But in that case why doesnt it just raise an exception like when applying assoc with a list. In the assoc case the philosophy is the same - the assoc should NOT be applied to a list since it doesnt support fast-enough random-access of its elements.

I feel it inconvenient because a lot of forms in clojure return a general collection as the results, such as (vals a-map), so to test if an element exists in the value set of a map, different methods give different results - but to me they are just the different ways of saying the SAME thing.


(def a-map {:one 1 :two 2})
(contains? (vals a-map) 1) ; returns false!!
(contains? (set (vals a-map)) 1); returns true!!

So after the long explanation, my question is - what is the rational behind this design? How should we say it natively when we want to test if an element is in a value set of a map in clojure, i.e., more importantly, how should I convince myself so that I will not make stupid bugs in practice? Thanks!

dolaameng
  • 1,397
  • 2
  • 17
  • 24
  • There is already an answer to you question on staksowerflow: http://stackoverflow.com/a/3249401/1202461. – Leonid Beschastny Jan 12 '13 at 10:17
  • 1
    Before asking any question you shall search for it. Probably, someone already asked it before. – Leonid Beschastny Jan 12 '13 at 10:18
  • 2
    Thanks for the comment, but I still believe that thread only answers part of my question here. Specially, why it is designed this way instead of how it is designed. And does it mean we should always work it around when using contains? with a list? – dolaameng Jan 12 '13 at 10:22
  • `contains?` tells you if the result from `get` is truly in the collection. If the latter does not apply, then neither does the former. – Alex Taggart Jan 13 '13 at 19:38
  • possible duplicate of [Test whether a list contains a specific value in Clojure](http://stackoverflow.com/questions/3249334/test-whether-a-list-contains-a-specific-value-in-clojure) – Till Helge Jan 14 '13 at 11:35

2 Answers2

4

The source code reveals that for clojure up to and including version 1.4 contains? always return false for lists (it falls through to line 713).

In the latest 1.5 beta it will throw an exception.

ivant
  • 3,909
  • 1
  • 25
  • 39
1

contains? is intended only for use on keyed collections (e.g. maps for arbitrary key / value pairs, vectors for indexed collections with integer indexes). See the docstring:

clojure.core/contains?
([coll key])
  Returns true if key is present in the given collection, otherwise
  returns false.  Note that for numerically indexed collections like
  vectors and Java arrays, this tests if the numeric key is within the
  range of indexes. 'contains?' operates constant or logarithmic time;
  it will not perform a linear search for a value.  See also 'some'.

Personally, I think it is badly named and should have been called has-key? or something like that. But that's past history - we're stuck with it unless someone (i.e. Rich decides to make a breaking change to the API).

Arguably though, if contains? ever causes you a problem then you are probably doing something wrong from an algorithmic perspective: you probably shouldn't be doing a sequential search of a collection to find a value, you should be using a map or set instead.

But to answer the question: I agree with you, it would make more sense for contains to throw an exception when applied to something that does not support keyed lookup like lists. It doesn't contradict the docstring since this behaviour is undefined, so maybe that is worth a patch?

EDIT: I see from @ivant's answer that the latest 1.5 beta does throw an exception on lists. Problem solved already!

mikera
  • 105,238
  • 25
  • 256
  • 415