キーワード + オプショナル引数
Clojureは組み込みではキーワード引数がサポートされてませんが、分配束縛(destructuring)を使うことで同様のことができるようになります。例はにあります。
リンク先にも書いてあるとおり、このパターンをマクロにしたものが、clojure.contrib.defに入っているdefnkです。でも、defnkでは少し物足りないことがあります。
例えば[a :b 1 :c 2 & rest]というように、デフォルト値付きキーワード引数に加えて、さらにオプショナルな引数を取りたい場合はdefnkではうまくいきません。defnkを参考に、オプショナル引数もサポートするdefnk*を書いてみました。
(defn take-while-nth [n pred coll] (lazy-seq (when-let [s (seq coll)] (when (pred (first s)) (concat (take n s) (take-while-nth n pred (drop n s))))))) (defn drop-while-nth [n pred coll] (lazy-seq (loop [p pred c coll] (when-let [s (seq c)] (if (p (first s)) (recur p (drop n s)) s))))) (defn split-with-nth [n pred coll] [(take-while-nth n pred coll) (drop-while-nth n pred coll)]) (defmacro defnk* [fn-name & fn-tail] (let [[args & body] fn-tail [req-args kv-and-rest] (split-with symbol? args) [key-vals extras] (split-with-nth 2 keyword? kv-and-rest) syms (map #(symbol (name %)) (take-nth 2 key-vals)) values (take-nth 2 (rest key-vals)) sym-vals (apply hash-map (interleave syms values)) de-map {:keys (vec syms) :or sym-vals} extras (nth extras 1)] `(defn ~fn-name [~@req-args & options#] (let [[key-vals# extras#] (split-with-nth 2 keyword? options#) ~de-map (apply hash-map key-vals#) ~extras extras#] ~@body))))
take/drop-while-nthとsplit-with-nthは補助関数です。recur/loopやlazy-seqの使い方が正しいか自信がない。。。
defnk*を使うとこんなことができるようになります。
user> (defnk* f [a :b 1 :c 2 & rest] (println a b c) (println rest)) #'user/f user> (f 0) 0 1 2 () nil user> (f 0 2 4 6) 0 1 2 (2 4 6) nil user> (f 0 :a 2 :b 4 6 8) 0 4 2 (6 8) nil
これは便利。