LoginSignup
29
25

More than 5 years have passed since last update.

目立たないけど便利な Clojure の関数を紹介してみる

Posted at

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

mapremove を足したような関数。 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 は楽しい言語ですね!

29
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
25