Implicit unpacking of a map

30 views
Skip to first unread message

Thorsten Wilms

unread,
Mar 24, 2011, 11:16:59 AM3/24/11
to clo...@googlegroups.com
Hi!

The following simplified code works:
----
(defn save-article
[path form-params timestamp]
(ds/save! (Article. path (form-params "title") (form-params "body")
timestamp))))
----

But I would like to not handle the content of form-params explicitly.

My naive attempt that shows that I still have troubles with the
evaluation model:
----
(ds/save! (flatten `(Article. path ~(vals form-params) timestamp)))
----
Fails with: "java.lang.IllegalArgumentException: No implementation of
method: :get-entity-object of protocol:
#'appengine-magic.services.datastore/EntityProtocol found for class:
clojure.lang.Symbol"

I take that I created a list that looks like the function call that I
need, but that is not evaluated as such. Throwing in eval doesn't help.
Can you shed some light on this?

How can I accomplish not having to make the content of form-params
explicitly? (Not to save on typing, more for my understanding.)


--
Thorsten Wilms

thorwil's design for free software:
http://thorwil.wordpress.com/

Tassilo Horn

unread,
Mar 24, 2011, 12:10:01 PM3/24/11
to clo...@googlegroups.com
Thorsten Wilms <t_...@freenet.de> writes:

Hi Thorsten,

> The following simplified code works:
> ----
> (defn save-article
> [path form-params timestamp]
> (ds/save! (Article. path (form-params "title") (form-params "body")
> timestamp))))
> ----
>
> But I would like to not handle the content of form-params explicitly.

Probably, you want to use destructuring.

--8<---------------cut here---------------start------------->8---
(defn save-article
[path {t "title", b "body" & rest :as m} timestamp]
(println "path:" path
", title:" t
", body:" b
", timestamp:" timestamp
", m:" m))
--8<---------------cut here---------------end--------------->8---

Bye,
Tassilo

Meikel Brandmeyer

unread,
Mar 24, 2011, 12:40:17 PM3/24/11
to Clojure
Hi,

On 24 Mrz., 16:16, Thorsten Wilms <t...@freenet.de> wrote:

> ----
> (defn save-article
>    [path form-params timestamp]
>    (ds/save! (Article. path (form-params "title") (form-params "body")
>       timestamp))))
> ----

The problem is the constructor call. With plain old clojure
functions you could use apply, but Java method and constructor
calls must be hard-wired in the bytecode (and hence at
compilation time) (if I understood this correctly).

So there will be now way around explicitly specifying the map
keys. Although you can relieve the pain a little with
destructuring as Tassilo already showed. It can be shorted a
bit using :strs.

(defn save-article
[path {:strs [title body]} timestamp]
(ds/save! (Article. path title body timestamp)))

Sincerely
Meikel

Thorsten Wilms

unread,
Mar 24, 2011, 4:28:42 PM3/24/11
to clo...@googlegroups.com
On 03/24/2011 05:40 PM, Meikel Brandmeyer wrote:

> The problem is the constructor call. With plain old clojure
> functions you could use apply, but Java method and constructor
> calls must be hard-wired in the bytecode (and hence at
> compilation time) (if I understood this correctly).

Guess that's why a macro, which I tried out of sheer curiosity, doesn't
work, either.

> So there will be now way around explicitly specifying the map
> keys. Although you can relieve the pain a little with
> destructuring as Tassilo already showed. It can be shorted a
> bit using :strs.
>
> (defn save-article
> [path {:strs [title body]} timestamp]
> (ds/save! (Article. path title body timestamp)))

I had been using destructuring, just not in the example, where I had to
have a whole form-params. :strs makes that quite a bit nicer, thanks!

Looking for a bit more info, I found:
http://groups.google.com/group/clojure/browse_thread/thread/a9504c4c9b1a4d9b
explaining :strs, :keys and :syms.

Alan

unread,
Mar 24, 2011, 4:38:45 PM3/24/11
to Clojure
A macro should work fine if you use ~@ instead of just ~.

(defmacro save-article
[path form-params timestamp]
`(ds/save! (Article. ~path ~@(vals form-params) ~timestamp)))

Meikel Brandmeyer

unread,
Mar 24, 2011, 4:45:14 PM3/24/11
to clo...@googlegroups.com
Hi,

Am 24.03.2011 um 21:38 schrieb Alan:

> A macro should work fine if you use ~@ instead of just ~.
>
> (defmacro save-article
> [path form-params timestamp]
> `(ds/save! (Article. ~path ~@(vals form-params) ~timestamp)))

A macro works only with literal maps.

Sincerely
Meikel

Alan

unread,
Mar 24, 2011, 4:55:09 PM3/24/11
to Clojure
I find it easier to write web apps if my users provide me with their
inputs at compile time anyway.

Thorsten Wilms

unread,
Mar 24, 2011, 4:56:16 PM3/24/11
to clo...@googlegroups.com
On 03/24/2011 09:38 PM, Alan wrote:
> A macro should work fine if you use ~@ instead of just ~.
>
> (defmacro save-article
> [path form-params timestamp]
> `(ds/save! (Article. ~path ~@(vals form-params) ~timestamp)))

Thanks for the suggestion, but:
----
Unknown location:
error: java.lang.IllegalArgumentException: Don't know how to create ISeq
from: clojure.lang.Symbol
----

(I doubt I managed to introduce a mistake, as it looks alright with
macroexpand.)

Ken Wesson

unread,
Mar 24, 2011, 8:34:06 PM3/24/11
to clo...@googlegroups.com
What about this? First, use :title and :body keywords instead of
"title" and "body" strings as keys in form-params. Then define this
utility function:

(defn get-seq [m & kws]
((apply juxt kws) m))

which takes a map and one or more keywords (cannot be other types of
key) and returns a seq (actually a vector) of the corresponding
values, in order; do this

(defn new-article [path timestamp title body]
(Article. path title body timestamp))

so you can use apply. And then this:

(defn save-article
[path form-params timestamp]
(ds/save!
(apply new-article path timestamp
(get-seq form-params :title :body))))

is neat and tidy and easily extensible to added form-params later (add
them to the end of new-article's arg list and to the get-seq keyword
list in the same order; pass them to the amended Article constructor
appropriately).

Thorsten Wilms

unread,
Mar 25, 2011, 5:09:12 AM3/25/11
to clo...@googlegroups.com
On 03/25/2011 01:34 AM, Ken Wesson wrote:
> What about this? First, use :title and :body keywords instead of
> "title" and "body" strings as keys in form-params. Then define this
> utility function:
>
> (defn get-seq [m& kws]

> ((apply juxt kws) m))
>
> which takes a map and one or more keywords (cannot be other types of
> key) and returns a seq (actually a vector) of the corresponding
> values, in order; do this

I don't get to chose the keys, with wrap-params I get whatever is used
in the form as strings, inside a :form-params inside the request map.

> (defn new-article [path timestamp title body]
> (Article. path title body timestamp))

Interesting, but you just listed title and body twice, where the goal
was to not list them at all (except in the definition of Article and in
the html form, of course, though theoretically, the form could be
generated from a a bit richer single definition, I suppose).


> so you can use apply. And then this:
>
> (defn save-article
> [path form-params timestamp]
> (ds/save!
> (apply new-article path timestamp
> (get-seq form-params :title :body))))

By now I'm rather sure this would trigger an error, as nothing but an
immediate call of Article. seems acceptable for ds/save!. Really not a
big deal, just Java-interop making things un-lispy.

> is neat and tidy and easily extensible to added form-params later (add
> them to the end of new-article's arg list and to the get-seq keyword
> list in the same order; pass them to the amended Article constructor
> appropriately).

Ken Wesson

unread,
Mar 25, 2011, 6:37:11 AM3/25/11
to clo...@googlegroups.com
On Fri, Mar 25, 2011 at 5:09 AM, Thorsten Wilms <t_...@freenet.de> wrote:
> On 03/25/2011 01:34 AM, Ken Wesson wrote:
>>
>> What about this? First, use :title and :body keywords instead of
>> "title" and "body" strings as keys in form-params. Then define this
>> utility function:
>>
>> (defn get-seq [m & kws]

>>   ((apply juxt kws) m))
>>
>> which takes a map and one or more keywords (cannot be other types of
>> key) and returns a seq (actually a vector) of the corresponding
>> values, in order; do this
>
> I don't get to chose the keys, with wrap-params I get whatever is used in
> the form as strings, inside a :form-params inside the request map.

Well, that's too bad. (apply juxt (map (fn [k] #(get % k)) kws)) it is, then.

>> (defn new-article [path timestamp title body]
>>   (Article. path title body timestamp))
>
> Interesting, but you just listed title and body twice, where the goal was to
> not list them at all (except in the definition of Article and in the html
> form, of course, though theoretically, the form could be generated from a a
> bit richer single definition, I suppose).

I extracted them from save-article to what's basically a
Clojure-friendlier constructor for Article. Is there a reason for you
to consider this goal supremely important?

>> so you can use apply. And then this:
>>
>> (defn save-article
>>   [path form-params timestamp]
>>   (ds/save!
>>     (apply new-article path timestamp
>>       (get-seq form-params :title :body))))
>
> By now I'm rather sure this would trigger an error, as nothing but an
> immediate call of Article. seems acceptable for ds/save!.

Why do you say that? Either your ds-save! is a function that accepts
Article objects, and that apply expression returns one; or else your
ds-save! is a macro that accepts an s-expression that will become part
of its expansion and which works if it evaluates to an Article object
at run-time, which the apply expression does.

If for some reason you've made it a macro that accepts an
s-expression, but parses it itself and expects it to be a constructor
invocation, then I suggest you rewrite it to accept any s-expression.
Otherwise, the style should be separate parameters, e.g. (defmacro
ds-save! [classname & ctor-args]) to make it simpler to implement and
to make it clear to its users that it cannot take general-purpose
sexps that return the appropriate Java type.

But really, it should accept pure function invocations happening in
its context, at least -- and the only possibly-impure thing in the
apply/getseq/new-article chain there is the Article constructor
invocation itself, which would be there anyway.

Meikel Brandmeyer

unread,
Mar 25, 2011, 6:55:04 AM3/25/11
to Clojure
Hi,

On 25 Mrz., 10:09, Thorsten Wilms <t...@freenet.de> wrote:

> Interesting, but you just listed title and body twice, where the goal
> was to not list them at all (except in the definition of Article and in
> the html form, of course, though theoretically, the form could be
> generated from a a bit richer single definition, I suppose).

Can you generate a "empty" article and set stuff with setters?
Then you could use reflection.

(defn save-article
[path form-params timestamp]
(let [article (Article.)]
(doto article
(.setPath path)
(.setTimestamp timestamp))
(doseq [[k v] form-params]
(let [setter (.getMethod article (str "set" k))]
(.invoke setter article v)))
(ds/save! article)))

I don't know how the reflection API works, so just some pseudo
calls to get across the idea. And you probably shouldn't use
this in speed critical code.

Sincerely
Meikel

Thorsten Wilms

unread,
Mar 25, 2011, 9:03:47 AM3/25/11
to clo...@googlegroups.com
On 03/25/2011 11:37 AM, Ken Wesson wrote:

>> Interesting, but you just listed title and body twice, where the goal was to
>> not list them at all (except in the definition of Article and in the html
>> form, of course, though theoretically, the form could be generated from a a
>> bit richer single definition, I suppose).
>
> I extracted them from save-article to what's basically a
> Clojure-friendlier constructor for Article. Is there a reason for you
> to consider this goal supremely important?

Yes, it was the whole reason to not be satisfied with just destructuring
in the signature. To be concise, avoiding any repetition. While perhaps
enlightening, anything that makes me end up writing more, not less, is
not practical.

>> By now I'm rather sure this would trigger an error, as nothing but an
>> immediate call of Article. seems acceptable for ds/save!.
>
> Why do you say that? Either your ds-save! is a function that accepts
> Article objects, and that apply expression returns one; or else your
> ds-save! is a macro that accepts an s-expression that will become part
> of its expansion and which works if it evaluates to an Article object
> at run-time, which the apply expression does.

I say that because of what Meikel said at
http://groups.google.com/group/clojure/msg/2f2b97b627da2d1d?hl=en
and because my experiments suggest so.

> If for some reason you've made it a macro that accepts an
> s-expression, but parses it itself and expects it to be a constructor
> invocation, then I suggest you rewrite it to accept any s-expression.
> Otherwise, the style should be separate parameters, e.g. (defmacro

> ds-save! [classname& ctor-args]) to make it simpler to implement and


> to make it clear to its users that it cannot take general-purpose
> sexps that return the appropriate Java type.
>
> But really, it should accept pure function invocations happening in
> its context, at least -- and the only possibly-impure thing in the
> apply/getseq/new-article chain there is the Article constructor
> invocation itself, which would be there anyway.

I didn't write it, it's part of appengine-magic. It appears in a
defprotocol and if I get this right, is mapped to a save!-helper
function in a defprotocol. So at my current level, I really couldn't
have written that :)
https://github.com/gcv/appengine-magic/blob/v0.4.0/src/appengine_magic/services/datastore.clj

Ken Wesson

unread,
Mar 25, 2011, 9:12:22 AM3/25/11
to clo...@googlegroups.com
On Fri, Mar 25, 2011 at 9:03 AM, Thorsten Wilms <t_...@freenet.de> wrote:
> On 03/25/2011 11:37 AM, Ken Wesson wrote:
>
>>> By now I'm rather sure this would trigger an error, as nothing but an
>>> immediate call of Article. seems acceptable for ds/save!.
>>
>> Why do you say that? Either your ds-save! is a function that accepts
>> Article objects, and that apply expression returns one; or else your
>> ds-save! is a macro that accepts an s-expression that will become part
>> of its expansion and which works if it evaluates to an Article object
>> at run-time, which the apply expression does.
>
> I say that because of what Meikel said at
> http://groups.google.com/group/clojure/msg/2f2b97b627da2d1d?hl=en
> and because my experiments suggest so.

He said you can't use (apply Article. constructor-arg
seq-of-constructor-args) and he's right -- you can't (apply Class.
...) or (apply .method object ...). But you *can* (defn function [x]
(Class. x)) and you *can* then (apply function
seq-of-constructor-args). That *should* work.

Reply all
Reply to author
Forward
0 new messages