Part of what's so powerful about Clojure is that all the core data-types implement the same sequence abstraction: clojure.lang.ISeq.
This means that functions like "first", "concat", "cons", "map", "rest", etc work generically on all of those data-types.
My question is this: how can I add my own custom function into the mix, and have it work for all the types that extend from ISeq?
One first attempt was to define my own protocol, then "(extend-type clojure.lang.ISeq ...", but that doesn't work (it compiles but doesn't add the behavior to the actual types). Another idea was to write a macro that does an "extend-type" explicitly on all the Clojure types (PersistentHashMap, PersistentList, etc), but that seems kludgey.
Is there any elegant/idiomatic way to do this? Multimethods perhaps?
What exactly are you trying to do?
If you're trying to add behaviour to existing types: either write normal functions that deal with seqs or use multimethods or extend
to do what you want.
Also, something to note is that most Clojure "sequence" types (vectors, sets, maps) are not in themselves sequences (they do not implement clojure.lang.ISeq
), so you have to do more than just add to clojure.lang.ISeq
if you wish to support them.
There's an IBM Developerworks article by Stuart Sierra titled Solving the Expression Problem with Clojure 1.2 that might provide insights and an answer to your question.
It uses protocols to define a group of functions on several data types, and uses extend
to extend existing classes (data types) so they can use these functions. That might not be exactly what you want, but it could be one way of solving your problem.
It also shows you how you could define custom data types (using defrecord
and deftype
) that implement existing protocols / interfaces.
The best way to do this is to write your new functions using existing generic Clojure functions that handle the different data types correctly.
Examples of such generic functions:
into
appends items to a collection of any typeempty
returns an empty collection of the same type as it's parameterThen you can write your own generic function that exploits these, e.g.:
(defn take-every-other [coll]
(into
(empty coll)
(map first (partition 2 coll))))
(take-every-other [1 2 3 4 5 6])
=> [1 3 5]
(take-every-other {:a 1 :b 2 :c 3 :d 4})
=> {:a 1, :c 3}
If you still need more generic function capability, you can always dive into the Clojure source to see how these functions are written.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With