Reputation: 5759
given the type:
(defn as-pairs [m]
(when-not (empty? m)
(seq (persistent! (reduce (fn [s [k vs]]
(reduce (fn [s v] (conj! s (clojure.lang.MapEntry/create k v))) s vs))
(transient []) m)))))
(deftype Rel [m]
clojure.lang.Seqable
(seq [this] (as-pairs m))
clojure.lang.ILookup
(valAt [this k] (get m k))
(valAt [this k default] (get m k default))
clojure.lang.IPersistentMap
(assoc [this k v] (Rel. (update m k #(conj (or % #{}) v))))
(assocEx [this k v] (throw (Exception.)))
(without [this k] (Rel. (dissoc m k))))
(defn relation [& pairs]
(reduce (fn [r [k v]] (assoc r k v)) (Rel. (hash-map)) pairs))
Is it possible to override the calls to (keys ...)
and (vals ...)
?
As an example, the current implementation
>(def r (relation [1 2] [1 3] [2 4]))
> r
{1 3, 1 2, 2 4}
> (get r 1)
#{3 2}
> (seq r)
([1 3] [1 2] [2 4])
> (keys r)
(1 1 2)
> (vals r)
> (3 2 4)
For example I would like keys
to return something more along the lines of (seq (set (keys r))
i.e exclude duplicates. I can see that in
static public class KeySeq extends ASeq{
final ISeq seq;
final Iterable iterable;
static public KeySeq create(ISeq seq){
if(seq == null)
return null;
return new KeySeq(seq, null);
}
...
It seems that keys
and vals
is dependent on the implementation of seq
for clojure.lang.Seqable
. it just takes the first/second values from the clojure.lang.MapEntry
pairs returned by (seq r)
.
Is there any way around this?
Upvotes: 1
Views: 173
Reputation: 13324
No, it is not possible to override this behavior of clojure.core/keys
and clojure.core/vals
. As you noted, those Clojure functions call the corresponding static methods in clojure.lang.RT
, which in turn call the createFromMap
static methods in the KeySeq
and ValSeq
classes of clojure.lang.APersistentMap
. All these implementations use IPersistentMap
directly to get their sequence, so they cannot be overridden independently of what you've already written.
One thing you could do in this case is provide your own keys
function to replace the one from clojure.core
:
(ns example.core
(:refer-clojure :exclude [keys])
(:require [clojure.core :as clj]))
(defn keys [m]
(distinct (clj/keys m)))
Example:
(ns example.usage
(:refer-clojure :exclude [keys])
(:require [example.core :refer [keys]]))
(keys {:foo :bar :baz :qux})
;;=> (:foo :baz)
One thing to note is that it is part of the contract of clojure.core/keys
to return a sequence that matches up with seq
, so, for instance, (= m (zipmap (keys m) (vals m)))
holds in general. This example.core/keys
function satisfies that property for regular Clojure maps, but not for the multimaps you're defining. It's up to you to decide whether that tweak of semantics is a good idea or not.
Upvotes: 3