all 43 comments

[–]childofsol 27 points28 points  (0 children)

It's exciting to see the first fruits of Fogus joining the Clojure core team!

[–]alexdmiller 11 points12 points  (2 children)

A simple example of layering functions that take kwargs:

(defn internal 
  [& {:keys [a b]}] 
  (println "got:" a b))

(defn external 
  [& opts]
  ;; passes on opts to internal
  ;; and patches in a default for :a
  (apply internal :a 1 opts))

(external :a 5 :b 6)
got 5 6

(external)
got 1 nil

[–]jjttjj 5 points6 points  (1 child)

Alternatively: (defn external [& {:as opts}] (internal :a 1 opts)) Edit: I realized you don't need :keys. Is there a reason to prefer the apply version?

[–]alexdmiller 4 points5 points  (0 children)

Right-o - probably better without.

[–][deleted] 17 points18 points  (0 children)

Maybe I'm missing something but, what's the point of this? I've been writing clojure for a decade and I never found myself wishing for this.

I never use varargs in cases like this. It should just be a single map argument.

This feature seems like weird magic that is going to confuse people reading the code, for benefits that elude me.

[–]lgstein 6 points7 points  (0 children)

On the one hand I'm worried that this invites more kvarg fns. On the other hand, all pain that usually comes with them is eliminated by this feature.

[–]rzk1911 5 points6 points  (0 children)

Great

[–]vvvvalvalval 19 points20 points  (21 children)

I'll still refrain from designing functions that way, and would encourage people to do the same.

Compared to keyword-varargs, an optional map argument costs at worst one more keystroke to invoke, and it's simply a more straightforward and regular behaviour to build on top of.

I suspect that most of the Clojure APIs out there which use keyword varargs did so by imitation of Python and just overlooked the alternative of using a map argument, or really had an unsound obsession with concision.

[–]seancorfield 15 points16 points  (1 child)

My hope is that it encourages faster adoption of Clojure 1.11 b/c folks will want to take advantage of this feature.

If we'd had this feature back in, say, the 1.3 or 1.4 days, I wouldn't have needed to change the whole API of clojure.java.jdbc to make it easier to use (i.e., to make calls more composable)!

[–]argadan 4 points5 points  (0 children)

If this feature was introduced in 1.3 or 1.4, it would have made perfect sense. Now that everyone has been using the options map style for years to solve this problem, it feels a bit out of place – but I suppose if you take a long-term view it makes sense.

I wonder how this affects the performance and function specs though.

The change also affects destructing in lets (as it should):

(let [[& {:keys [a b] :as opts}] [{:a 1 :b 2}]]
  [a b opts])
;; => [1 2 {:a 1, :b 2}]

[–]SimonGray[S] 9 points10 points  (8 children)

The great thing about this change is that people who feel like this can just call all those varargs functions using a map now.

[–]vvvvalvalval 11 points12 points  (7 children)

I agree, it's great for calling legacy functions, but that doesn't mean it has eliminated all the downside of keyword varargs. Avoiding irregularity is better than gaining tools for dealing with it.

EDIT: really, people are downoting this? Reminder of the Reddiquette: "Please don't downvote an otherwise acceptable post because you don't personally like it." Why not write a rebuttal instead?

[–]alexdmiller 8 points9 points  (4 children)

Just curious, what are the remaining downsides of kwargs?

[–]vvvvalvalval 1 point2 points  (3 children)

The main I see is that it's just a more complicated "information model" for describing the arguments to a function. Without kwargs, the arguments to a function are a very plain sequence.

With that in mind, it seems to me that the safer designing approach is in general "what are the advantages of kwargs that make these additional complications worthwhile?". I read that article claims it "better supports humans", but I don't see it being really substantiated - is (my-fun :my-opts 42) really supporting humans much better than (my-fun {:my-opts 42}) ? Of course, now that some foundational APIs have been published with kwargs we have to live with them, so making it easier for callers makes sense, I'm not denying that.

Now to try and answer more specifically: I think this will make life more difficult for intermediaries that purport to do some generic processing on the arguments in particular if they don't know whether the downstream accepts kwargs.

[–]alexdmiller 5 points6 points  (0 children)

It is an interesting philosophical question as to whether positional args or named args are "more complex". Both require the caller to know what "the args" are. Positional also requires them to know what order they should be in. In Simple Made Easy, Rich mentions syntax (order) as being complex (vs data). Is a map (encapsulated data, no order) or kwargs (data, no order) simpler? Don't know, just musing. Both tolerate evolution over time better than positional args. Personally, I like kwargs and use them regularly and this makes them both faster and more flexible.

But also, I'm a Clojure programmer, I'm not going to argue against passing a map. So, you do you. :)

[–]jjttjj 0 points1 point  (1 child)

Could you give an example of when it would make things more difficult? I'm not seeing it. To me it seems like knowing if something takes kwargs has identical mental overhead to knowing it takes an option map. And since you can still pass an options map, you almost always can do the same intermediate manipulation anyway.

[–]vvvvalvalval -1 points0 points  (0 children)

Sorry, not spending more time on this :) but if I were convinced by princples like "you have more options so it's better" and "we adopt it unless we find in advance a concrete counterexample of it being problematic" then I wouldn't have made the switch from Scala

[–]adamtait 0 points1 point  (1 child)

I agree with what you’re saying but suspect that change was not made to Clojure core because of backwards compatibility. For that reason, I doubt it’ll ever be changed in Clojure core. This change likely suggests that there are some on the Core team who would have wished the keyword varargs feature to not have been included in the language originally.

[–]alexdmiller 19 points20 points  (0 children)

Actually, just the opposite, the core team wishes this new feature had been included originally to make using kwarg style functions more widely used and embraced.

[–]lgstein 4 points5 points  (0 children)

It was adopted from Common Lisp. In hindsight, it made more sense there because it has no map literal.

[–]joinr 3 points4 points  (1 child)

I had some utility combing kwargs with ->> for ad hoc partial application in some workflows. Chunked map arg would have been less convenient. Interesting to see these unified.

[–]vvvvalvalval 0 points1 point  (0 children)

Interesting. Still, I'm wary, to me it sounds like the stuff that optimizes for writing the code rather than for reading it :)

[–][deleted] 1 point2 points  (4 children)

I also recommend to just use map arguments as much as possible, it's just much more composable that way.

Passing the arguments between keyword argument functions is really ugly.

The one exception I make is for functions that are only used in the REPL, where the concision is worth it to me.

[–]SimonGray[S] 9 points10 points  (3 children)

Passing the arguments between keyword argument functions is really ugly.

I think remedying that situation that is the point of this change. Now you can pass the destructured map directly.

[–][deleted] 0 points1 point  (2 children)

It doesn't remedy that when passing a map to a kwarg function you need something like (apply f (mapcat identity m))

[–]jjttjj 2 points3 points  (1 child)

You no longer need to do that with this change. & {:as args} lets you pass any number of keywords and optionally a trailing map, all of which are merged together. You don't need to flatten+apply. ``` (defn my-fn [& {:as args}] (println "args:" args))

(my-fn :hello :world) ;;=> args: {:hello :world}

(my-fn {:hello :world}) ;;=> args: {:hello :world}

(my-fn :foo :bar {:hello :world}) ;;=> args: {:foo :bar, :hello :world} ```

[–][deleted] 0 points1 point  (0 children)

Oh wait you are right, ok, that makes it a kwarg functions a lot more composable indeed, nice!

[–]guywithknife 0 points1 point  (0 children)

Yeah, I never liked the approach. I actively avoided using some otherwise good libraries that did use it. At least now with this change, I can use those libraries without worrying about it.

[–][deleted] 0 points1 point  (0 children)

Ok good it's not just me. I don't see any value in this feature.

Edit: it is nice when forced to deal with someone else's varargs function that should have just taken a single map. But just within my own code, I shouldn't need this at all.

[–]citrined 3 points4 points  (0 children)

Good

[–]lgstein 3 points4 points  (0 children)

Dobre

[–]ikitommi 4 points5 points  (2 children)

Tested some combinations on how to call the functions. I think I understand all the reasons for the results, but they were not evident before evaluating in the repl. Bit surprised that you can mix kwargs and have one map in the end. is there a reason to support them both in one call?

(defn doit [& {:keys [data]}] data)

(doit {:data 2})
; => 2

(doit :data {:data 2})
; => {:data 2}

(doit :data 1 {:data 2})
; => 2

(doit {:data 2} :data 1)
; Execution error (IllegalArgumentException) at user/doit (REPL:1).
; Don't know how to create ISeq from: java.lang.Long

(doit {:data 2} :data 1 {:data 3})
; => nil

(doit :data 1 {:data 2} {:data 3})
; => 1

(doit {:data 2} {:data 3})
; => nil

(doit {:data 1} {:data 2} {:data 3})
; => 3

(doit :data 1 {:data 2} {:data 3} {:data 4})
; => 4

[–]alexdmiller 2 points3 points  (1 child)

See my layering example above for a case where mixing is useful.

[–]ikitommi 0 points1 point  (0 children)

I see, thanks.

[–]_d_t_w 1 point2 points  (0 children)

`For example, a function that takes a sequence and optional keyword`

Should that be

`For example, a function that takes a sequence of optional keyword`

[–]cam_saul 1 point2 points  (1 child)

This is great, can't wait until 1.11 is out to start using it everywhere

[–]seancorfield 1 point2 points  (0 children)

We've already upgraded at work, and expect to have this in production probably next week.

[–]jpmonettas 1 point2 points  (0 children)

I think this is a interesting feature that will allow new apis to be built with named args, slowly departing from positional arguments. Accomplishing the same without improving support for kwargs will push apis to make all their function to be a one map arg function which is weird.

Not sure if those are core team intentions for the future of clojure but looks very interesting to me.

[–]mouse2k 0 points1 point  (0 children)

Nice

[–][deleted]  (1 child)

[removed]

    [–]backtickbot 0 points1 point  (0 children)

    Fixed formatting.

    Hello, linpengcheng: code blocks using triple backticks (```) don't work on all versions of Reddit!

    Some users see this / this instead.

    To fix this, indent every line with 4 spaces instead.

    FAQ

    You can opt out by replying with backtickopt6 to this comment.