There is, in my view, an inconsistency with the clojure core API with regards to type checking.
Consider the two functions contains? and even?
contains? returns true if a certain collection has a certain key:
=> (contains? {'a 1} 'a) true => (contains? {'a 1} 'b) false
If you pass an object which is not a collection, contains? silently returns false.
=> (contains? 1 'a) false
I.e., a type error is not distinguishable from a collection which does not contain a certain element.
even? , the function to check if a certain number is, well, even, behaves in a completely different fashion:
=> (even? 'a) java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.lang.Number (NO_SOURCE_FILE:0)
A type error on the parameter raises an exception with even?.
From a design philosophy I really do not like this inconsistent behavior: For the same type of error (a typical error pattern) the API behaves in a clearly different way.
The 2 points that are important (but, alas, are not the fundamental issue of this post) are:
- Core functions should have a coherent and consistent way of dealing with type errors. This is the most important point.
- If they have a consistent way of dealing with type errors, my preference would be for a behavior like even? (ie, throw) and not like contains?.
Yes, I do understand that other reasons might have taken precedence (like performance). I still don’t like it.
But the beauty of clojure is that one can redefine these “core” functions. For instance, I prefer to have
(defn contains? [coll key] (if (coll? coll) (clojure.core/contains? coll key) (throw (new ClassCastException (str (type coll) " cannot be cast as collection"))) ) )
[Newbie alert: there might be better ways to design a function like this (suggestions welcome).]
Now, one as to be careful not to import the original contains? into a namespace. Easy done in clojure:
(ns myuser (:refer-clojure :exclude [contains?]))
This has to be done before the definition of the new contains? (note that, when calling the old contains? it includes the full namespace).
Of course, redefining core functions (even if inside namespaces not seen outside) is a bit like laying a mine field and probably has to be done with care. Irrespective of that, it is good to be able to have a language which allows one to express itself with the syntax and semantics that one desires, and not to be constrained to the whims of the original developer.
So far, no big problems found with Clojure (at least until now).
