Rabid wild animals in my clojure argument lists, code gets infected.

192 views
Skip to first unread message

Dave Tenny

unread,
Aug 22, 2015, 7:34:11 PM8/22/15
to Clojure
I sure wish Clojure would generate "IllegalArgumentException" in the following sample case:

(defn foo [a b & {:keys [c d]}] 1)
(foo 1 2 :e 5) ; blithely ignores :e 5

I understand that Clojure's destructuring things are very nice, and more powerful than Common Lisp's, and I like
that when I need it.

However I can't tell you how many times I've been bitten by this. Some simple typo or other other parameter name error on keyword arguments
with untended and way-too-long-to-debug consequences.

In my mind, on this subject, Common Lisp lambda lists got this right and Clojure gets a poor grade.
Something about being doomed to repeat history.  In Common Lisp, if you really wanted to allow other (arbitrary) keywords you'd
just use &allow-other-keys.  

Maybe clojure should only allow the above case to go un-complained-about if :as was specified for the map.

If there's some automatic enforcement I'm missing that comes with 'defn' please let me know, I'm still learning the language.

I've thought more that once about making a common lisp DEFUN statement that maps to DEFN but implements
lambda list semantics (including 'supplied-p' parameters).  I've just been too lazy to do it.  

It would also likely perform poorly after injecting the additional checks/rearrangements into the function on top of what Clojure has already done,
so I suppose it would have to be taken a step further so that it didn't generate DEFN expressions at all but was implemented at DEFN's level as 
a seperately named form.

Tips welcome.  Just thinking aloud.

- Dave

Colin Yates

unread,
Aug 22, 2015, 9:07:03 PM8/22/15
to clo...@googlegroups.com
Hi Dave, it _isn't_ an Illegal argument though :-), your destructuring
is simply ignoring that parameter.

However, I get the pain and solutions might be (in order of 'heavyness'):
- http://blog.fogus.me/2009/12/21/clojures-pre-and-post/
- https://github.com/Prismatic/schema
- http://typedclojure.org/

HTH
--
Sent with my mu4e

James Reeves

unread,
Aug 22, 2015, 9:22:08 PM8/22/15
to clo...@googlegroups.com
https://github.com/roomkey/annotate is another possibility

- James


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Timothy Baldridge

unread,
Aug 23, 2015, 10:12:14 AM8/23/15
to clo...@googlegroups.com
It's generally considered better practice to pass maps to a function instead of keyword arguments. This also has the nice side-effect of making it easier to call programmatically  from other functions. For example:

(my-func 1 2 (assoc default-opts :c 42)) is way cleaner than

(apply my-func 1 2 (mapcat identity (assoc default-opts :c 42)))

So don't do keyword argument, it rarely works out well. Instead accept a map of options and validate it with asserts, ignoring "extra" data. A map is a map, it shouldn't matter if it contains extra data. 

Timothy
--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Dave Tenny

unread,
Aug 23, 2015, 10:17:53 AM8/23/15
to clo...@googlegroups.com
The point of my colorful title and judgmental post is that in the example, passing :e for the declared parameters as declared is a pointless operation at best, and in most practical situations it is also an erroneous operation. 

The language designers have (arguably for the good) chosen to complain about various actual argument mismatches vs. formal parameters.

If I tried to call foo with only one argument I'd get an error about missing arguments (or more generally, arity).
Similarly, in this case, calling foo with :e should complain about superfluous arguments, all assuming that foo is declared as intended.
Without an :as option on the destructuring, there is nothing that can be done to meaningfully process a keyword to the function named :e.

I am proposing the language should do better in this case. If not now, perhaps 2.0. 


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/3-CevvzQg1s/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Dave Tenny

unread,
Aug 23, 2015, 10:21:08 AM8/23/15
to clo...@googlegroups.com
Note Colin, if I had declared foo differenlty, e.g. (defn foo [a b {:keys [c d]}), that would be slightly different since it requires a map as third argument.  But that is not the case with
(defn foo [a b & {:keys [c d]}]).  It may be using maps under the hood, but none of my formal parameters are necessarily intended to be bound to maps in this case, and I'm looking for a very fixed set of keywords in the function call.


Dave Tenny

unread,
Aug 23, 2015, 10:30:59 AM8/23/15
to clo...@googlegroups.com
Timothy, I think maps vs keywords is a matter of preference.  Certainly there are cases where I use maps of options instead of keywords, but it depends on what I'm doing.   Lisp and keywords go way back, and I like keywords when I'm using lisp packages/namespaces as the equivalent of an interactive query language.   

Even for the case of pure options passed as a map, you'll still potentially have cases where you might like to validate that only *expected* keywords were used. Whether a map as argument or a :as option to destructuring. And it's all well and good to let the user of those maps write code to enforce that.   

However in the traditional use of keywords naming formal parameters there's a long Common Lisp (at least) history of validating these bindings,
and there's also compile time capabilities too, though perhaps less effectively in Clojure than CL.  Clojure has provided these keyword capabilities, 
I just object to this one hole because it has zero up side that I can see, and substantial downside to writing correct programs.

You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/3-CevvzQg1s/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

James Reeves

unread,
Aug 23, 2015, 11:34:12 AM8/23/15
to clo...@googlegroups.com
On 23 August 2015 at 15:17, Dave Tenny <dave....@gmail.com> wrote:
The point of my colorful title and judgmental post is that in the example, passing :e for the declared parameters as declared is a pointless operation at best, and in most practical situations it is also an erroneous operation. 

The language designers have (arguably for the good) chosen to complain about various actual argument mismatches vs. formal parameters.

Clojure uses destructuring to handle keyword arguments. If we want destructuring keyword arguments to behave differently to destructuring a map, then we make the language less consistent. Ideally there would be a separate syntax for keyword arguments.

In general Clojure doesn't have great support for keyword arguments. They're supported by destructuring, but they don't work with apply, and as you've discovered, don't throw exceptions when you get the keys wrong.

- James

dmic...@gmail.com

unread,
Aug 25, 2015, 3:01:36 AM8/25/15
to Clojure
If you have assertions enabled, you can

declare


(defn foo [a b & {:keys [c d]}]
{:pre [c d]}
1)

to make sure that c and d are not nil

(defn foo [a b & {:keys [c d] :as m}]
{:pre [(every? m [:c :d]]}
1)

to make sure that c and d are contained and

(defn foo [a b & {:keys [c d] :as m}]
{:pre [(zero? (count (dissoc m :c :d)))]}
1)

to make sure that m contains only :c and :d

It will only complain at run-time but at least it will complain.
Message has been deleted

dmic...@gmail.com

unread,
Aug 25, 2015, 3:09:44 AM8/25/15
to Clojure
Sorry, I answered too quickly:

The second one was wrong - this one ensures c and d are contained.

(defn foo [a b & {:keys [c d] :as m}]
{:pre [(every? (partial contains? m) [:c :d]]}
1)

And the last one was un-idiomatic - this one checks whether only c and d are contained.


(defn foo [a b & {:keys [c d] :as m}]
{:pre [(empty? (dissoc m :c :d))]}
1)


Reply all
Reply to author
Forward
0 new messages