Skip to content

Instantly share code, notes, and snippets.

@devn
Created September 5, 2014 21:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devn/c52a7f5f7cdd45d772a9 to your computer and use it in GitHub Desktop.
Save devn/c52a7f5f7cdd45d772a9 to your computer and use it in GitHub Desktop.
Implementing IFn
(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