Subscribed unsubscribe Subscribe Subscribe

キーワード + オプショナル引数

Clojure

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

これは便利。