apply for keyword-argument functions

424 views
Skip to first unread message

Herwig Hochleitner

unread,
Aug 20, 2012, 9:49:03 AM8/20/12
to cloju...@googlegroups.com
Good day,

currently, it's not easy to apply a function onto a map of keyword-arguments. 
Here is an unoptimized example implementation:

(defn apply-kw
  "Like apply, but f takes keyword arguments and the last argument is
  not a seq but a map with the arguments for f"
  [f & args]
  {:pre [(map? (last args))]}
  (apply f (apply concat
                  (butlast args) (last args))))

I'm aware that one should use keyword destructuring only for DSL like functions, that get called immediately.
There are still use cases when building out layers of a DSL, where apply-kv comes in very handy, where you would otherwise need to define two almost identical functions: One with a map, one with keywords.

Shall there be a ticket for that, given that we already have destructuring syntax for such functions?

kind regards

Stuart Sierra

unread,
Aug 20, 2012, 5:04:37 PM8/20/12
to cloju...@googlegroups.com
I have not come across this in practice. If the situation arises where I need to pass a map, then I'd just make the function argument a map.

I expect that this function would not be accepted into the core language right now, but it might have a home in a contrib library.
-S

Allen Rohner

unread,
Aug 20, 2012, 8:23:36 PM8/20/12
to cloju...@googlegroups.com
Anecdotally, I have come across this in practice. I've written my own version of apply-kw. The main reason it occurs in my own code is that you never know what the "highest" level function will be. i.e first you write

(defn foo [arg & {:keys [opt1 opt2]}])

And then some time later, you decide you need to write

(defn bar [arg & {:keys [opt1 opt2 opt3]}]
  (foo :opt1 opt1 :opt2 opt2))

Having apply-kw is very useful in this situation. 

Herwig Hochleitner

unread,
Aug 20, 2012, 8:24:27 PM8/20/12
to cloju...@googlegroups.com
2012/8/20 Stuart Sierra <the.stua...@gmail.com>

I have not come across this in practice. If the situation arises where I need to pass a map, then I'd just make the function argument a map.

My feeling is: I'd rather not let one case dictate the function signature, if varargs feel more elegant for the majority of call sites.

### (skip to next ###, when skimming)

Please let me introduce you to my motivating use case in point: 

I'm creating a DSL for java source code generation.
There is a DSL entity: (defn field [& {:keys [modifiers type name init]}] ...) which is used to emit class members and local variables.

e.g. (field :modifiers ["public" "static"] :type "int" :name "memberOne") becomes "public static int memberOne;" during emission.

You can see that an end user of the DSL might use that a lot. I'd rather not write an additional set of curlies there. But of course, that `field` entity is also used in a few higher level constructs, like generating a builder pattern. I need to pass a map there, but it's internal to the library.

So I have two choices there:
- have the function take a map and create a stub for end users => not very DRY
- use apply-kw

Needless to say I went with the third choice:
- create a macro `defkwfn`, that generates a polyadic function, which can take a single map argument or kw args

which is actually the first choice, only with the function and the stub collapsed into one fn.
I dont expect that `defkwfn` macro to show up in core anytime soon ;-)

### (skip blurb to here)
 
I expect that this function would not be accepted into the core language right now, but it might have a home in a contrib library.

I feel core is appropriate, because `apply-kw` is complementary to that nifty kw-arg destructuring syntax we received (not so) recently.
We've got `apply` as a complement to regular varargs.

OTOH dissoc-in is still in incubator, so I understand symmetry and conceptional completeness are not the only relevant factors.
 
-S

To be clear: I see _a lot_ of value in having a small core, but I also see value in symmetry and having basic building blocks there, when you need them. Especially those that promote changes to APIs, when missing.

Also: functional, algorithmic code doesn't rot.

kind regards

Stuart Sierra

unread,
Aug 22, 2012, 11:32:15 AM8/22/12
to cloju...@googlegroups.com
Ultimately, it's not my decision. You're welcome to submit a patch if you have a contributor agreement.
-S

travis vachon

unread,
Dec 2, 2012, 5:12:14 PM12/2/12
to cloju...@googlegroups.com
Just wanted to chime in here even though this is a pretty old thread because I think I have a pretty decent real world use case in some Pallet work we're doing.

Pallet's a good example of a DSL-y language designed to be used interactively that can and should be abstracted on-top-of: it's useful to use it to launch and administer machines at the REPL, but building higher level abstractions (clusters, systems, rather than single machines) is natural and should be well supported. Building these abstractions has, for us, meant building a map of keyword arguments over time that we'd like to pass to Pallet functions that utilize kw arg, and that in turn has lead us to copy apply-kw from above:

Pallet shouldn't need to choose between being a good interactive tool and being easily built-upon, and apply-kw means it doesn't need to.

That doesn't mean this should be in core yet, but it does seem like something that should at least incubate in contrib for a bit - does anyone know if this made it into a contrib library somewhere? I haven't spent a ton of time investigating this yet - does anyone have thoughts on where this could live? I have a contributor agreement and would be happy to pick this ball up if there's interest.

t

Alex Baranosky

unread,
Dec 2, 2012, 11:29:43 PM12/2/12
to cloju...@googlegroups.com
I've definitely run into the issue where you've got one keyword-arg function that delegates to another keyword-arg function.  You tend to get duplication in those cases.  I'm taking not of apply-kw so that I can use it to clean up some of those spots next time I'm in those areas of the code.

Alex

--
You received this message because you are subscribed to the Google Groups "Clojure Dev" group.
To view this discussion on the web visit https://groups.google.com/d/msg/clojure-dev/-/y_c4ZhLG4zcJ.

To post to this group, send email to cloju...@googlegroups.com.
To unsubscribe from this group, send email to clojure-dev...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en.

Herwig Hochleitner

unread,
Dec 3, 2012, 12:27:07 PM12/3/12
to cloju...@googlegroups.com
Ticket and patch for core.incubator created: http://dev.clojure.org/jira/browse/CINCU-3
Reply all
Reply to author
Forward
0 new messages