Created
September 5, 2014 21:40
-
-
Save devn/c52a7f5f7cdd45d772a9 to your computer and use it in GitHub Desktop.
Implementing IFn
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn gen-nonvariadic-invokes [f] | |
(for [arity (range 1 21), | |
:let [args (repeatedly arity gensym)]] | |
`(~'invoke [~@args] (~f ~@args)))) | |
(defn gen-variadic-invoke [f] | |
(let [args (repeatedly 22 gensym)] | |
`(~'invoke [~@args] (apply ~f ~@args)))) | |
(defn gen-apply-to [f] | |
`(~'applyTo [this# args#] (apply ~f this# args#))) | |
(defn extend-IFn [f] | |
`(clojure.lang.IFn | |
~@(gen-nonvariadic-invokes f) | |
~(gen-variadic-invoke f) | |
~(gen-apply-to f))) | |
(defmacro deftypefn | |
"Like deftype, but accepts a function f before any specs that is | |
used to implement clojure.lang.IFn. f should accept at least one | |
argument, 'this'." | |
[name [& fields] f & opts+specs] | |
`(deftype ~name [~@fields] | |
~@(extend-IFn f) | |
~@opts+specs)) | |
(deftypefn CsvRecord [indices->columns columns->indices m] | |
(fn [this mapfn & args] | |
{:post [(= (set (keys (merge columns->indices (.-m %)))) | |
(set (keys columns->indices)))]} | |
(CsvRecord. indices->columns | |
columns->indices | |
(apply mapfn m args))) | |
clojure.lang.Indexed | |
(nth [this i] (get m (get indices->columns i))) | |
(nth [this i default] (get m (get indices->columns i))) | |
clojure.lang.ILookup | |
(valAt [this k] (get m k)) | |
(valAt [this k default] (get m k default)) | |
clojure.lang.Seqable | |
(seq [this] (filter val (map #(clojure.lang.MapEntry. % (get m %)) (vals indices->columns)))) | |
clojure.lang.IPersistentCollection | |
(cons [this o] | |
(if (instance? clojure.lang.IPersistentMap o) | |
(CsvRecord. indices->columns | |
columns->indices | |
(merge m (select-keys o (keys columns->indices)))) | |
(let [[k v] o] | |
(if v | |
(do (assert (contains? columns->indices k)) | |
(CsvRecord. indices->columns columns->indices (assoc m k v))) | |
this)))) | |
(count [this] (count indices->columns)) | |
(empty [this] (CsvRecord. (sorted-map) {} {})) | |
(equiv [this x] (and (instance? CsvRecord x) | |
(= indices->columns (.-indices->columns x)) | |
(= m (.-m x)))) | |
clojure.lang.Associative | |
(containsKey [this k] (contains? m k)) | |
(entryAt [this k] | |
(let [v (.valAt this k ::not-found)] | |
(when (not= v ::not-found) | |
(clojure.lang.MapEntry. k v)))) | |
(assoc [this k v] (conj this [k v])) | |
clojure.lang.IPersistentMap | |
(assocEx [this k v] | |
(if (.containsKey this k) | |
(throw (Exception. "Key or value already present")) | |
(assoc this k v))) | |
(without [this k] | |
(CsvRecord. indices->columns columns->indices (dissoc m k)))) | |
(defn csv [field-names] | |
(fn [fields] | |
(CsvRecord. (into (sorted-map) (map vector (range) field-names)) | |
(zipmap field-names (range)) | |
(into {} (filter val (zipmap field-names fields)))))) | |
(comment | |
(def row (csv ["first" "last" "age"])) | |
(def r1 (row ["Bob" nil "13"])) | |
(nth r1 0) ;=> "Bob" | |
(nth r1 1) ;=> nil | |
(nth r1 2) ;=> 13 | |
(get r1 "first") ;=> "Bob" | |
(get r1 "last") ;=> nil | |
(get r1 "age") ;=> "13" | |
(def r2 (r1 update-in ["age"] #(str (inc (Integer/parseInt %))))) | |
r2 ;=> {"first" "Bob", "age" "14"} | |
(def r3 (r2 assoc "last" "Dobbs")) | |
(nth r3 1) ;=> "Dobbs" | |
;; Here we can leverage the fact that r3 is invokable in order to | |
;; get a CsvRecord type back from a select-keys operation. | |
;; (select-keys r3 ...) would normally return a Clojure map, not a | |
;; CsvRecord. | |
(def r4 (r3 select-keys ["first" "last"])) | |
r4 ;=> {"first" "Bob", "last" "Dobbs"} | |
(class r4) ;=> user.CsvRecord | |
(def r5 (r4 merge (row [nil nil "25"]))) | |
r5 ;=> {"first" "Bob", "last" "Dobbs", "age" "25"} | |
(def r6 (r5 merge {"first" "Joe"})) | |
r6 ;=> {"first" "Joe", "last" "Dobbs", "age" "25"} | |
(class r6) ;=> user.CsvRecord | |
(def r7 (r6 into [["age" "17"]])) | |
r7 ;=> {"first" "Joe", "last" "Dobbs", "age" "17"} | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment