Clojure 楽しいですね。今年の 8 月くらいから勉強していて、いい機会なのでアドベントカレンダーに参加してみようと思いました。
私はあまりまだ Clojure に慣れてないので clojure.core のネームスペースからあまり目立たないけど意外に便利な関数を紹介してみたいと思います。
alter-meta!
他のネームスペースから Var を持ってきて別名をつけようとしたりするときに、落ちてしまうメタ情報を付与したいときに使えます。
example.core> (def my-inc #'clojure.core/inc)
#'example.core/my-inc
example.core> (clojure.repl/doc my-inc)
-------------------------
example.core/my-inc
nil
nil
example.core> (alter-meta! #'my-inc merge
(select-keys (meta #'clojure.core/inc) [:doc :arglists]))
{:line 37, :column 14, :file "*cider-repl example*", :name my-inc, :ns #object[clojure.lang.Namespace 0x3b450d77 "example.core"], :doc "Returns a number one greater than num. Does not auto-promote\n longs, will throw on overflow. See also: inc'", :arglists ([x])}
example.core> (clojure.repl/doc my-inc)
-------------------------
example.core/my-inc
([x])
Returns a number one greater than num. Does not auto-promote
longs, will throw on overflow. See also: inc'
nil
bit-flip
データベースに真偽値を直接入れることが出来なくて、 tinyint なんかで実装するとデータベース上では 0 or 1 になるんですが、これを反転させたいときとかに便利です。
example.core> (bit-flip 1 0)
0
example.core> (bit-flip 0 0)
1
bound?
動的束縛を使う場合、宣言時に初期値を与えないケースがあって、そういうときに nil?
などとして確かめようにも値に束縛されていないので false
になってしまいます。
そういうときに何にも束縛されてないことを確かめるために使います。
example.core> (def foo)
#'example.core/foo
example.core> (bound? foo)
ClassCastException clojure.lang.Var$Unbound cannot be cast to clojure.lang.Var clojure.core/bound?/fn--5267 (core.clj:5283)
example.core> (bound? #'foo)
false
butlast
rest
の逆になります。一番最後だけを取り除きたいときというのはたまにあるので覚えておくと使えます。
example.core> (butlast [1 2 3])
(1 2)
dedupe
これはあまり使わなさそうですが、 distinct
のように全ての重複を取り除く( like set
)のではなくて、連続した重複のみを排除します。
example.core> (dedupe [1 2 3 3 4 5 1 2 3])
(1 2 3 4 5 1 2 3)
extenders
プロトコルが何に継承されているのか知ることが出来ます。
example.core> (defprotocol Plus1 (plus1 [x]))
Plus1
example.core> (extend-protocol Plus1 String (plus1 [x] (str x 1)))
nil
example.core> (extend-protocol Plus1 Number (plus1 [x] (inc x)))
nil
example.core> (extenders Plus1)
(java.lang.String java.lang.Number)
fnil
たまに使います。ある関数に nil
が渡されたときに nil
の代わりに渡すものを予め決めた新しい関数を返す関数です。
example.core> ((fnil inc 0) nil)
1
example.core> ((fnil inc 0) 1)
2
keep
map
と remove
を足したような関数。 map
したあとに nil
を削除したいときに使えます。
example.core> (defrecord Product [code name])
example.core.Product
example.core> (defn new-product
([code] (map->Product {:code code}))
([code name] (map->Product {:code code :name name})))
#'example.core/new-product
example.core> (def products
[(new-product "CLJ01" "clj-v1")
(new-product "CLJ02" "clj-v2")
(new-product "CLJ03" "clj-v3")
(new-product "CLJ04")
(new-product "CLJ05")])
#'example.core/products
example.core> (keep :name products)
("clj-v1" "clj-v2" "clj-v3")
max-key
全ての引数に対して関数を適用して返ってきた数値が一番大きいものを返します。
example.core> (apply max-key - (range 10))
0
example.core> (apply max-key val {:foo 100 :bar 20 :baz 50})
[:foo 100]
example.core> (max-key :age {:name "miu" :age 22} {:name "mixu" :age 20})
{:name "miu", :age 22}
ns-unalias
特定のネームスペースからエイリアスを削除します。 REPL でエイリアス付けてしまったけど使わなくなったときなどに使えます。
example.core> (require '[clojure.string :as str])
nil
example.core> (str/join ", " (range 10))
"0, 1, 2, 3, 4, 5, 6, 7, 8, 9"
example.core> (ns-unalias *ns* 'str)
nil
example.core> (str/join ", " (range 10))
CompilerException java.lang.RuntimeException: No such namespace: str, compiling:(*cider-repl example*:171:14)
rand-nth
主にテストデータを作るときなどに重宝します。
example.core> (rand-nth [:foo :bar])
:bar
example.core> (rand-nth [:foo :bar])
:foo
reduce-kv
reduce
に対してマップを渡したときによく使う (fn [x [k v]])
をもっと素直に書けるようにした関数です。
example.core> (reduce-kv #(+ %1 %3)
0
{:foo 10
:bar 20
:baz 30})
60
take-nth
シーケンスから偶数個目だけを取り出すなどということが簡単に出来ます。
example.core> (defn coerce [& bindings]
{:pre [(zero? (rem (count bindings) 3))]}
(let [params (take-nth 3 bindings)
fns (take-nth 3 (drop 2 bindings))]
(map #(%1 %2) fns params)))
#'example.core/coerce
example.core> (coerce 42 :as str,
5/2 :as long)
("42" 2)
when-first
シーケンスの先頭だけを束縛する when
です。
example.core> (when-first [x (range 10)]
(prn x))
0
nil
まとめ
簡単ですが、私が便利だなって思う関数を紹介してみました。 Clojure は楽しい言語ですね!