like-some?
ccfontes opened this issue · comments
Because I use your plumbing
already in my benrikuro
and then just defcopy
the forms I like, it would be nice if some of other forms I use myself would have a chance to be integrated in plumbing
.
;;;;;;;;;;;;;;;; Somewhere from the Clojure community ;;;;;;;;;;;;;;;;
(defmacro defcopy
"Defines a copy of a var: a new var with the same root binding (if
any) and similar metadata. The metadata of the copy is its initial
metadata (as provided by def) merged into the metadata of the original.
source: same as defalias from clojure 1.2 and downwards."
([name orig]
`(do
(alter-meta!
(if (.hasRoot (var ~orig))
(def ~name (.getRawRoot (var ~orig)))
(def ~name))
;; When copying metadata, disregard {:macro false}.
;; Workaround for http://www.assembla.com/spaces/clojure/tickets/273
#(conj (dissoc % :macro)
(apply dissoc (meta (var ~orig)) (remove #{:macro} (keys %)))))
(var ~name)))
([name orig doc]
(list `defcopy (with-meta name (assoc (meta name) :doc doc)) orig)))
(defn ffilter ; can be find-first too
"Returns the first item of coll for which (pred item) returns logical true.
Consumes sequences up to the first match, will consume the entire sequence
and return nil if no match is found."
[pred coll] (first (filter pred coll)))
(defn map-dregs [f & colls] ; by TimMc
"Like map but when there is a different count between colls, applies input fn
to the coll values until the biggest coll is empty."
((fn map* [f colls]
(lazy-seq
(when (some seq colls)
(cons (apply f (map first (filter seq colls)))
(map* f (map rest colls))))))
f colls))
(defn update
"Updates the value in map m at k with the function f.
Like update-in, but for updating a single top-level key.
Any additional args will be passed to f after the value."
([m k f] (assoc m k (f (get m k))))
([m k f x1] (assoc m k (f (get m k) x1)))
([m k f x1 x2] (assoc m k (f (get m k) x1 x2)))
([m k f x1 x2 & xs] (assoc m k (apply f (get m k) x1 x2 xs))))
(defn update-in*
"Updates a value in a nested associative structure, where ks is a sequence of keys and f is a
function that will take the old value and any supplied args and return the new value, and returns
a new nested structure. If any levels do not exist, hash-maps will be created. This implementation
was adapted from clojure.core, but the behavior is more correct if keys is empty and unchanged
values are not re-assoc'd."
[m keys f & args]
(if-let [[k & ks] (seq keys)]
(let [old (get m k)
new (apply update-in* old ks f args)]
(if (identical? old new)
m
(assoc m k new)))
(apply f m args)))
(defn update-each
"Update the values for each of the given keys in a map where f is a function that takes each
previous value and the supplied args and returns a new value. Like update-in*, unchanged values
are not re-assoc'd."
[m keys f & args]
(reduce (fn [m key]
(apply update-in* m [key] f args))
m keys))
(defn update-vals ; by Jay Fields
"Updates all the values of a map"
[m f & args]
(reduce (fn [r [k v]] (assoc r k (apply f v args)))
{} m))
(require '[clojure.reflect :refer [reflect]] '[clojure.pprint :refer [print-table]])
(defn get-members [some-type]
(->> (-> some-type reflect :members)
(filter :exception-types)
(sort-by :name)
print-table))
(defn call-method
"Calls a private or protected method.
params is a vector of classes which correspond to the arguments to
the method e
obj is nil for static methods, the instance object otherwise.
The method-name is given a symbol or a keyword (something Named)."
[klass method-name params obj & args]
(-> klass (.getDeclaredMethod (name method-name)
(into-array Class params))
(doto (.setAccessible true))
(.invoke obj (into-array Object args))))
(defn get-field
"Access to private or protected field. field-name is a symbol or
keyword."
[klass field-name obj]
(-> klass (.getDeclaredField (name field-name))
(doto (.setAccessible true))
(.get obj)))
(defn unlazy
"Same as map/filter/reduce, but preserves the input data type."
[core-f f coll]
(into (empty coll) (core-f f coll)))
;;;;;;;;;;;;;;;; my own ;;;;;;;;;;;;;;;;
(defn update-multi
"Updates multiple keys of a map with multiple fns using a map of key/fn pairs."
[m fn-m]
(merge
m
(into {} (map (fn [[k f]] [k (f (k m))]) fn-m))))
(defn str->stream [string] (-> string .getBytes clojure.java.io/input-stream))
(def nilify (constantly nil))
(defn or->
"Same as -> but defaults to the initial value if the result is falsey."
[x & args]
(or (eval `(-> ~x ~@args)) x))
Thank you for taking the time.
Thanks for your interest in contributing.
We're pretty mindful of keeping plumbing.core
small -- just to those things we wish were in clojure.core
, more or less. That's a pretty high bar; internally, we have a plumbing.core-incubator
namespace, and only things that marinade in there for a few months and see a lot of usage are considered for inclusion.
Are there any of these that you find particularly useful and think meet this standard? FWIW, update
is already in there.
That's cool. Then I'm going to use the incubator namespace if it can reduce my codebase. Btw, can you provide me a link to that namespace? (it's embarrassing but I can't find it haha)
The ones I would really like to see in clojure.core
:
defcopy
- useful for aliasing a form while keeping its metadata. I use it to keep the docstring for aliases.ffilter
orfind-first
- this one doesn't need introduction.update-each
- useful for updating severalmap
keys
with samefn
.update-multi
- useful for updating severalmap
keys
with severalfns
.or->
- too often do I find myself using the original value when->
returnsnil
(but can be used forfalse
too).unlazy
- this one is just so useful, when you want to revert from thelazy-seq
to the input type when doing map/reduce/filter.map-dregs
would be cool too forclojure.core
, but I'm not sure how frequently it would be used.
update-multi
and unlazy
are names I came up with. They could be named differently.
Well defcopy
wont work in cljs and current impl of or->
also.
Cool, thanks. core-incubator
is an internal thing currently -- it's by definition unstable, so not sure it's something we actually want to expose publicly.
defcopy
: we're currently using potemkin/import-vars
for this.
find-first
: a deliberate omission -- we decided it was not shorter/clearer enough to merit inclusion.
update-each
: how often do you want this rather than map-keys
?
update-multi
: could see this as useful, but I'm not sure how often. If anywhere, it would probably go in plumbing.map -- will have to think about it.
or->
: when do you find yourself using this? I don't think I've really seen this pattern (and using eval
like this is a big no-no IMO).
unlazy
: I think mapv
fills most of this niche for us, and I think being explicit is often a good thing. I think this also reverses the order for lists?
potemkin/import-vars
looks neat. defcopy
allows me to additionally rename vars, which import-vars
doesn't (it seems). I do rename them a lot btw :) I even make copies of the var in the same ns
, for different naming tastes.
I use map-keys
and update-each
. The former updates all keys with one fn, the latter updates values of selected keys with one fn
, so they are quite different. Maybe the naming is ambiguous. An example: (update-each {:a 1 :b 2 :c 4} [:a :b] inc) => {:a 2, :c 4, :b 3}
update-multi
: I use it 2 times here and it's just 128 LOC. It may be some design fault on my part, though haha.
or->
: I'm only using it in the same source file, even though I felt the need for it in the past many times. The example is too simple, though. I think of it as defaulting to the original value when the result is false. Maybe the name is not the best either. I found some issues with the fn
(besides eval of course) so better ignore it for the moment. Sorry about this one. It's not mature enough.
unlazy
: yes, it reverses lists, well spotted! I guess I never tried it with lists haha. Being explicit is sound. Which fn
do you use for maps?
So it leaves us with defcopy
, update-each
and update-multi
. Do you want to leave defcopy
out also?
Thanks for the details. I think we'll skip worrying about defcopy
for now, until we do something with potemkin. For update-multi
, after looking at these examples, I think just a ->
with multiple calls to update
is clearer, and nearly as short. Do you have concrete examples of where you use update-each
?
Sorry for being a stickler about this stuff -- but I've spent way too much time over the years cleaning up huge namespaces of my utility functions that seemed like a good idea at the time but in hindsight weren't really net wins.
Yes, one could do that with ->
. My point there was to save 1 line. Maybe it's not as readable if each key must go in a different line anyway, but if it's a small one liner, I think it's a win, like this one in that file: (update-multi {:allFeaturesArray seq :partOfSpeech #(split % #",")})
. I would argue that if it's a 1 liner, readability may increase, when comparing to :keys
scattered through lines + ->
.
I found just one example of update-each
in my codebases:
(defn sync-timecodes [unsynced-sub synced-sub]
(let [time-offset (- (-> synced-sub :events first :Start)
(-> unsynced-sub :events first :Start))]
(update-in unsynced-sub [:events]
(partial map
#(update-each % [:Start :End] + time-offset)))))
Both :Start
and :End
would naturally be updated with the same fn
when shifting time by an offset
.
Don't worry, I'm already learning a lot with this ping pong and it was a pleasure :)
It's clear by now there isn't an obvious need of any of the remaining forms, so I'm closing this.
For future reference, you may want to see defcopy
, update-each
and update-multi
.
Thanks for the discussion. It was really useful for me.
Thanks for understanding -- we'll keep these in mind for future updates.