instar icon indicating copy to clipboard operation
instar copied to clipboard

can't provide my own dissoc-like fn

Open Bill opened this issue 8 years ago • 5 comments

dissoc apparently operates on the map that has the matching key:

(transform {:a 1 :b 2} [:a]
  dissoc)

=> {:b 2}

Since dissoc is a fn of (map, key), I ought to be able to provide my own, more selective, fn of (map,key), right?

(transform {:a 1 :b 2}  [:a]
  (fn [m k] 
    (if (= (m k) 1) 
      (dissoc m k) 
      m)))

ArityException Wrong number of args (1) passed to: normalize/eval1841/fn--1842  clojure.lang.AFn.throwArity (AFn.java:429)

An exact duplicate of the original example above, should be:

(transform {:a 1 :b 2}  [:a]
           (fn [m k]
               (dissoc m k)))

ArityException Wrong number of args (1) passed to: normalize/eval1881/fn--1882  clojure.lang.AFn.throwArity (AFn.java:429)

This prompted me to look at the source, from which I see that the dissoc fn is treated as a special case.

I'd like to be able to provide my own custom dissoc-like fn. Barring that, is there a way to operate on particular associations this way?

I see that a fn in this position only receives one argument: the value of the association:

(transform
  {:a 1} 
  [:a]
  (fn [v]
    (println "v: " v)
    2)) 

v:  1
=> {:a 2}

That fn can change the value of the association, but cannot dissoc it.

Of course, I gave it a whack with capture groups, but I couldn't make that work.

Bill avatar Nov 15 '17 15:11 Bill

I think I need to understand your use case better, because it makes no sense to me to supply your own dissoc in this context.

(transform foo [:x :y] dissoc) is just a convenient shorthand for (transform foo [:x] #(dissoc % :y))

I hope I got that right, haven’t done Clojure in a while so am quite rusty :P

boxed avatar Nov 15 '17 17:11 boxed

Here's my UC. I have a map:

(def m {:x "5" :y ""})

I want a fn that will transform the map, dissoc-ing keys, whose values are blank?. So:

(= {:x "5"} (foo m))

=> true

To do this with transform, the predicate would need to see the collection (associative) and the key, and it would need to be able to return the replacement collection.

As you have shown @boxed, dissoc is a shorthand that hard-codes the value from the path (in your example :y). Maybe if I could stick a fn there like:

(transform m [:x blank?] dissoc)

edited: no that isn't quite right because blank? would apply to the key—but I want it to apply to the value in this case. The brainfart continues…

Then that might become something like:

(transform m [:x] #(dissoc % blank?))

But of course dissoc won't allow a fn as the second parameter alas.

edited: we now return to your regularly-scheduled, mostly-coherent discussion.

Here's a foo that takes a predicate:

(defn foo [m pred]
  (into {}
        (remove
          (fn [[k v]](pred v))
          m)))

and it works:

(= {:x "5"} (foo {:x "5" :y ""} blank?))
=> true

I could use foo for a closely-related UC:

I have a map:

(def m2 {:x [1] :y []})

I want a fn that will transform the map, dissoc-ing keys, whose values are empty?. So:

(= {:x [1]} (foo m2 empty?))

=> true

Bill avatar Nov 15 '17 18:11 Bill

Hmm, yea we don't have any way to match on the values which you've understood is what you're really asking for. One could imagine something like:

(transform m [:x (%>value blank?)] dissoc)

What do you think @crisptrutski ?

boxed avatar Nov 17 '17 13:11 boxed

Could you branch on the arity of the provided function? If it is 1, then pass in the value at the path as an argument. If it is 2, then pass in the collection and the path as arguments? And explicitly bail out noisily when someone tries to use a multimethod?

devurandom avatar Mar 24 '19 22:03 devurandom

That sounds reasonable too. One could imagine supporting arity 3 toF or even more for some other feature. I'm thinking out loud now but how about that the arguments could be: value, key, path, container where key is in. That seems like it would cover lots of use cases.

boxed avatar Mar 25 '19 06:03 boxed