defnk addition to c.c.def

159 views
Skip to first unread message

Meikel Brandmeyer

unread,
Jun 4, 2009, 6:06:27 PM6/4/09
to clo...@googlegroups.com, Stephen C. Gilardi
Hello Stephen,

the following is a macro defining a function with keyword
arguments. It is called like this:

(defnk foo
[a b c :x 1 :z 2]
(println x z))

a, b and c denote positional arguments, 1 and 2 are default
values for x resp. z. This pattern came up several times now,
and there were always some "aha" effects. So it may be a
nice addition to c.c.def.

Here's the macro:
(defmacro defnk
"Define a function accepting keyword arguments. Symbols up to the
first
keyword in the parameter list are taken as positional arguments.
Then
an alternating sequence of keywords and defaults values is
expected. The
values of the keyword arguments are available in the function body by
virtue of the symbol corresponding to the keyword (cf. :keys
destructuring).
defnk accepts an optional docstring as well as an optional metadata
map."
[fn-name & fn-tail]
(let [[fn-name [args & body]] (name-with-attributes fn-name fn-tail)
[pos kw-vals] (split-with symbol? args)
kw-vals (apply hash-map kw-vals)
de-map {:keys (vec (keys kw-vals))
:or kw-vals}]
`(defn ~fn-name
[~@pos & options#]
(let [~de-map (apply hash-map options#)]
~@body))))

The docstring is a bit contorted but I'm too sleepy now,
to get that right...

Sincerely
Meikel

Meikel Brandmeyer

unread,
Jun 4, 2009, 6:22:42 PM6/4/09
to clo...@googlegroups.com
Hi again,

Am 05.06.2009 um 00:06 schrieb Meikel Brandmeyer:

> The docstring is a bit contorted but I'm too sleepy now,
> to get that right...

And of course I'm too sleepy to miss the keyword to
symbol conversion....

(defmacro defnk
"Define a function accepting keyword arguments. Symbols up to the
first
keyword in the parameter list are taken as positional arguments.
Then
an alternating sequence of keywords and defaults values is
expected. The
values of the keyword arguments are available in the function body by
virtue of the symbol corresponding to the keyword (cf. :keys
destructuring).
defnk accepts an optional docstring as well as an optional metadata
map."
[fn-name & fn-tail]
(let [[fn-name [args & body]] (name-with-attributes fn-name fn-tail)
[pos kw-vals] (split-with symbol? args)

syms (map #(-> % name symbol) (take-nth 2
kw-vals))
values (take-nth 2 (rest kw-vals))
sym-vals (apply hash-map (interleave syms
values))
de-map {:keys (vec syms)
:or sym-vals}]


`(defn ~fn-name
[~@pos & options#]
(let [~de-map (apply hash-map options#)]
~@body))))

Sincerely
Meikel

Sean Devlin

unread,
Jun 4, 2009, 6:54:57 PM6/4/09
to Clojure
Gut gemacht!

Absolutely amazing Meikel. Now get some well earned sleep.

Sean
>  smime.p7s
> 5KViewDownload

Stephen C. Gilardi

unread,
Jun 4, 2009, 7:02:43 PM6/4/09
to clo...@googlegroups.com

On Jun 4, 2009, at 6:54 PM, Sean Devlin wrote:

> Gut gemacht!
>
> Absolutely amazing Meikel. Now get some well earned sleep.
>
> Sean

I agree. It's a really beautiful piece of code, packed full of Clojure
goodness.

Nicely done!

I checked it into clojure.contrib.def:

http://code.google.com/p/clojure-contrib/source/detail?r=889

Thanks!

--Steve

Sean Devlin

unread,
Jun 5, 2009, 8:53:24 AM6/5/09
to Clojure
I'm writing a numerical methods library, and I already found a use for
you macro. Here's the old way:

(defn solve
[f & params]
(let [param-map (merge
{:start 1
:target 0
:diff-method :forward
:iter-method :newton
:epsilon 0.00000001
:max-iters 100}
(apply hash-map params))
diff-method (param-map :diff-method)
iter-method (param-map :iter-method)
start (param-map :start)
target (param-map :target)
max-iters (param-map :max-iters)
epsilon (param-map :epsilon)
f-point #(- (f %) target)
iter-methods {:newton (fn[x]
(- x (/ (f-point x) ((f-prime f-
point :diff-method diff-method) x))))}
iterator (iter-methods iter-method)]
(loop [x start
iter-count 0]
(let [next-x (iterator x)]
(cond
(< (java.lang.Math/abs (- (f-point x) (f-point next-x)))
epsilon) next-x
(> iter-count max-iters) (do
(println "Maximum number of
iterations reached")
nil)
true (recur next-x (inc iter-count)))))))


And here's the new way with your macro

(defnk solve-key
[f
:start 1
:target 0
:diff-method :forward
:iter-method :newton
:epsilon 0.00000001
:max-iters 100]
(let [f-point #(- (f %) target)
iter-methods {:newton (fn[x]
(- x
(/ (f-point x)
((f-prime f-point :diff-method diff-method) x))))}
iterator (iter-methods iter-method)]
(loop [x start
iter-count 0]
(let [next-x (iterator x)]
(cond
(< (java.lang.Math/abs (- (f-point x) (f-point next-x)))
epsilon) next-x
(> iter-count max-iters) (do
(println "Maximum number of iterations reached")
nil)
true (recur next-x (inc iter-count)))))))

So it's paying off already!
>  smime.p7s
> 3KViewDownload

Meikel Brandmeyer

unread,
Jun 6, 2009, 3:54:44 AM6/6/09
to clo...@googlegroups.com
Hi,

Am 05.06.2009 um 00:22 schrieb Meikel Brandmeyer:

> sym-vals (apply hash-map (interleave syms
> values))

Ah! I always forget about zipmap...

Clojure is just fun! :)

Sincerely
Meikel

Reply all
Reply to author
Forward
0 new messages