Perc: Anonymous Parameters by Association

perc let’s you do things like this:

#%/%(Triangle. %1:x %1:y %1:z,
               %2:x %2:y %2:z,
               %3:x %3:y %3:z)

Play with it right now using this command:

clj -Sdeps '{:deps {johnmn3/perc {:git/url "https://github.com/johnmn3/perc" :sha "676a444fa7f0eb23cea02832edb5f52ce6bc97d4"}}}' -m cljs.main -c perc.core -re node -r

More info here: https://github.com/johnmn3/perc

Lemme know if you have any questions!

1 Like

Another cool perc I just noticed you can do :slight_smile:

Return Literals

As per the updated docs, you can do:

cljs.user=> (#%/%{::a (inc %1) ::b (inc %2)} 4 5)
#:cljs.user{:a 5, :b 6}

Or

cljs.user=> (#%/%[(inc %:x) (inc %:y)] {:x 4 :y 5})
[5 6]

This makes for quick and easy restructuring of data in flight.

So, instead of having to do things like:

((fn [a1 a2] [a1 (inc a2)]) 4 5)

Or:

(#(do [%1 (inc %2)]) 4 5)

You can just do:

(#%/%[%1 (inc %2)] 4 5)

Enjoy!

1 Like

Oh hey, i did something similar recently (a macro rather than a reader-macro).

This still a work in progress (and eventually I intend to use it as a replacement for the #( reader-macro (:hear_no_evil: :see_no_evil: :speak_no_evil: )).

What’s funny is that %1:some-key is on my road-map but I also implemented %1name. name means nothing, it’s just to enhance readability.

Here’s the beast (still a work in progress).

#?(:clj
    ;; TODO: nested calls to (%| ...) and #(...)
    ;; IMP:  some computations are repeated unnecessarily
    ;; IMP:  use a dance
    (defmacro %| [& expr]
      {:test (fn []
               (are [x y] (= y (macroexpand x))
                    '(%| + %1 %2)         '(fn* ([%1 %2]     (+ %1 %2)))
                    '(%| + %  %2)         '(fn* ([%1 %2]     (+ %1 %2)))
                    '(%| + %a %b)         '(fn* ([%1a %2b]   (+ %1a %2b)))
                    ;; unused args
                    '(%| + %2b %3c)       '(fn* ([%1 %2b %3c] (+ %2b %3c)))
                    ;; a %NUM can refer to a previous %NAME
                    '(%| + %abc %2 %1)    '(fn* ([%1abc %2] (+ %1abc %2 %1abc)))
                    ;; a %NAME never refer to a previous %NUM
                    '(%| + %1 %2 %abc)    '(fn* ([%1 %2 %3abc] (+ %1 %2 %3abc)))
                    ;; but a %NAME can refer to a previous %NUMNAME
                    '(%| + %1abc %2 %abc) '(fn* ([%1abc %2] (+ %1abc %2 %1abc)))
                    ;; and a %NUMNAME can refer to a previous %NUM
                    '(%| + %1 %2 %1abc)   '(fn* ([%1 %2] (+ %1 %2 %1)))
                    ))}
      (let [%-syms      (atom {})
            args-by-num (fn [m]
                          (->> (keep  #(when (-> % key number?) %)  m)
                               sort))
            next-num    (fn [m]
                          (-> m args-by-num last
                              (as-> e (if e (key e) 0))
                              inc))
            new-expr
            (clojure.walk/postwalk
              (fn [form]
                (if-let [[_ n nme] (and
                                     (simple-symbol? form)
                                     (when-let [v (re-matches #"%(\d+)?(\D.*)?"
                                                              (name form))]
                                       (update
                                         v 1 #(when % (Integer/parseInt %)))))]
                  (letfn [(handle-n [m]
                                    (if (and n (not (m n)))
                                      (assoc m n (symbol (str \% n nme)))
                                      m))
                          (handle-nme [m]
                                      (if (and nme (not (m nme)))
                                        (let [n (or n (next-num m))
                                              m (assoc m nme
                                                  (or (m n) (symbol
                                                              (str \% n nme))))
                                              m (assoc m n (m nme))]
                                          m)
                                        m))
                          (handle-none [m]
                                       (if (and (not n) (not nme))
                                         (assoc m 1 (symbol "%1"))
                                         m))]
                    (get (swap! %-syms (->| handle-n handle-nme handle-none))
                         (or n nme 1)))
                  form))
              expr)
            %-syms        (deref %-syms)
            used-args     (vals %-syms)
            arg-count     (->> used-args sort last name
                               (re-matches #"%(\d+).*")
                               second Integer/parseInt)
            args          (vec (for [n (range 1 (inc arg-count))]
                                 (get %-syms n (symbol (str \% n)))))]
        `(fn ~args
           ~new-expr))))
1 Like

This is really cool. I’m not sure how readable it is, but definitely cool.

The first thing I tried was %& to see if you’d implemented that – and it worked – so I then wondered if %&:x would work, wondering if it would pull :x out of each argument, but it didn’t (not suggesting you implement that, it was pure curiosity on my part!).

1 Like

I intend to use it as a replacement for the #( reader-macro

Heresy! :scream: :wink:

My first reaction to writing %3:x was ‘eww, what is this, perl?’… but after trying it out for a while, it’s starting to feel more and more Clojurey - insofar as lots of other parts of the language have these dual indexical vs associative strengths (destructuring, lookup, etc), where-as sugared anon fns only have this indexical quality.

I was also stink-eyed towards messing with %& or doing anything over aggregates… but now that you described the behavior, it seems sort of intuitive :thinking:

1 Like
#%/%(Triangle. %1:x %1:y %1:z
               %2:x %2:y %2:z
               %3:x %3:y %3:z)
#(Triangle. (:x %1) (:y %1) (:z %1)
            (:x %2) (:y %2) (:z %2)
            (:x %3) (:y %3) (:z %3))

Hum :thinking:

(fn [{x1 :x y1 :y z1 :z}
     {x2 :x y2 :y z2 :z}
     {x3 :x y3 :y z3 :z}]
  (Triangle. x1 y1 z1
             x2 y2 z2
             x3 y3 z3))

Hum :thinking:

I don’t know. I kind of hate them all, at the same time, I could get used to each one and be okay using them or reading them.

I feel you about hating them all :rofl: There’s always tradeoffs. it is neat though that tagged literals allow us to explore and experiment with things like this.

So the second and third examples you showed are pretty much what the first one expands to. Something like:

(fn [local%]
  (Triangle. (:x (nth local% 0)) (:y (nth local% 0)) (:z (nth local% 0))
    ... ))

The second example is a bit heavy on the parenthesis. The third example repeats each number and letter three times.

The first form is a little condensed looking. I’m just not used to it yet and I’m not sure about the aesthetics of it. Does it look noisy to you?

Based on an example from the readme, if you had something like this:

{::demo/events [e1 e2 e3]
 :acme/event-handler (fn ...
 ::time-out-callback (fn ...
 ... }

And you can do this:

#%/%(mapv
      #%/$(%:acme/event-handler $:event/id $:event/data
            #%/?(%::time-out-callback ?:error/id ?:error/time-out-msg))
      %::demo/events)

How would you most concisely write that using normal syntax? Something better than:

(fn [{events ::demo/events
      time-out-callback ::time-out-callback
      event-handler :acme/event-handler]
  (mapv
    (fn [{:keys [event/data event/id]}]
      (event-handler id data
       (fn [{error-id :error/id msg :error/time-out-msg}]
         (time-out-callback error-id msg))))
    events)

Would you expect %&:x to filter by :x on the args or map over them and return nils for failures?

Intuitively for me, I’d expect it to return something like given:

[:x 1 :y 2 :z 3]
%&:y ;; would return 2

Because normally when I use vararg for associative, it is as an inline representation as such.

Ay caramba, the intuitions are diverging!

Okay, well what would you expect %&:y to return for the input of ({:x 1} {:y 2} {:z 3})? Also 2?

1 Like

Because you’re thinking of this destructuring? & {:keys [x y z]}

Yeah, there’s an argument for that. You’ve persuaded me that %&:k should work for that case and not map over the arguments. So I rescind my original idea in favor of this one!

1 Like

Are y’all talking about making it compatible with keyword arguments, so like from the docs:

(defn configure [val & {:keys [debug verbose]
                        :or {debug false, verbose false}}]
  (println "val =" val " debug =" debug " verbose =" verbose))

Could be done like:

(def configure
  #%/%(println "val =" %1
               " debug =" (or %&:debug false)
               " verbose =" (or %&:verbose false)))

So you could pass to anonymous function interfaces by keyword:

(configure 12 :verbose true :debug true)

Or were you thinking of something else?

1 Like

Keyword arguments, per my example of destructuring, yes.

Yeah that’s a pretty good idea. Shouldn’t be too hard.

Are you sure this is really useful (%& being understood as denoting a map rather than a sequence) ?

Looking in my Clojure folder, (some forks, some personal projects), here is what I find about the use of %&:

#(apply update-nth-next-context %1 (dec n) f %&)
#(apply update-nth-in %1 ks f %&)
#(reset! err %&)
#(first %&)
#(apply prev-f %&)
#(apply partial f (concat %& args))
#(apply f (butlast %&))
...

In short: I did no find a single instance where I used %& as a “flattened” map.
Also I don’t really see the point of

(def configure
  #%/%(println...

when compared to the variant that uses defn. As I said above I developed something similar to write re-frame code in this style:

(reg-event-db :db/initialize    (hnd| #(load-initial-state % config)))
(reg-event-db :db/set-wallet    (hnd| assoc))
(reg-event-db :db/set-time      (hnd| (db->| assoc-in sanitize-time)))
(reg-event-db :db/set-time-mode (hnd| (%| assoc-in %db [%time-name :mode] %v)))

My goal was to get something short like #( but with readable args like fn just to fit stuff in one single line. This way my eyes can perform queries on the code.

Using this %| function I have indeed felt the use to fetch sub-arguments within the function’s top level args, but I was thinking of implementing it in the style of get-in which means:

  • deep access
  • can access both sequential and associative data-structures
  • support for keywords and (named) nums as access keys
(filter #%/%(online? %2config:1net:jdbc:host) ;; or %2:1:jdbc:host
        vms-dict)

This way we can get something balanced, as all things should be, for both sequential and associative access.

No, %& denotes a sequence of arguments of variable length.

The question is, what would you expect to happen if you ask for a key from such a sequence? Like given:

(:x [...])
;; or
#(:x %&)

What would you expect this to do?

In destructuring, Clojure currently assumes that the sequence must be coerced to an associative structure such as by doing:

(apply hash-map [...])

So if you use %& you still get a sequence of args. If you do %&:x though… That’s where personally I felt I’d expect it to do a similar thing as destructuring, and assume that my args are an alternating of key/value pair, and I want the value of :x to be returned from that.

Do you have any common use case where you instead expect to call an anonymous function with a sequence of maps which all have a duplicate key for which you want to extract the values of or filter for only the maps that have the given key?

Echoing @didibus’s comments, what would be your preferred behavior?

Also I don’t really see the point of

(def configure
  #%/%(println...

when compared to the variant that uses defn

Well, I didn’t mean to suggest def’ing top level forms with #%/%, but it just made for a convenient example from the official docs to show. Normally we’d use anon variants of fns in middle steps of a transformations, rather than as top level definitions. Honestly, I don’t see that %&:x would be used very much, since keyword args are most useful for humans, rather than in the uniform applications anon fns are usually used in. But who knows what people come up with…

I was thinking of implementing it in the style of get-in

Yeah, I discussed this with a colleague the other day. I may add %*[:a 2 :b], where the vector is a get-in path into %, and % is something like {:a [0 1 {:b ....

There’s also %*{...} which could potentially dispatch to some EQL thing or Meander’s find or some ideal way to query Clojure data structures.

#(f %&)  ===  (fn [& args] (f args))
#(f %&)  =!=  (fn [& {:as args}] (f args))

So with Perc I would expect

#(f %&:x)  ===  #(f (nth %& :x))
  ==> ClassCastException class clojure.lang.Keyword cannot be
      cast to class java.lang.Number

Instead I would prefer something that behaves like this

#(f %&:1)  ===  #(f (nth %& 0))

Elaborating on John’s example:

(#%/%[(inc %:x) (inc %:y)] {:x 4 :y 5})

I’m lead to write

(#%/%[(inc %:1) (inc %:2)] '(4 5))

Edit: now that I read what I wrote again, that I advocate access by indexes doesn’t I’m against access by keys. I just want both – and as explained above, it makes the %&:x dilemma dissipates. Then the logical next step is deep access à la get-in (or should I say get-nth-in 1 since it need to works for all sequential and associative datastructures)

I see what you mean, like could it support full access for both associative and sequential, in a deeply nested way. I’m not sure exactly how.

Maybe with a vector you can kind of do it:

%1[:x 2 :y 3]

This could be from the first argument, get the value of key :x, now if that value is associative, get the value of key 2, but if it is sequential, get the second element, then from that get the value of key :y, and again, depending on if that is sequential or associative, get either the value of key 3 or the 3rd element.

If that syntax can work, it would also support non keyword keys like:

%2["x"]

Get the value for key x from the second argument.

And now you mean in that case, %& should be treated as a normal sequential?

%&[:x] ;; This wouldn't be valid
%&[1] ;; This would get the first element of the vararg

I still think that’s weird and not helpful for vararg though. In the case of vararg, you don’t really know the order or number of args passed in. Generally, you’ll probably want either to get them all, or maybe get the first and second or last of them, or like I said, you’d want to use it as a way to do named parameters where the vararg is an alternate of key/values and you’d want to be able to get the value for the key even though it’s a sequential. Supporting all that would feel a bit convoluted to me. But I don’t know, maybe there’s a clever syntax that could do it cleanly.

1 Like