Clojure Contrib

Clojureの準標準的なライブラリ群であるContribライブラリを紹介します。

core.async

core.asyncは非同期プログラミングをサポートします。

チャネル

core.asyncではチャネルを用いて値をやりとりします。

チャネルの生成にはchanを使い、チャネルに値を書き込むには>!!、読み込むには<!!を使います。

これらの関数は操作が完了するまでスレッドをブロックすることから、通常futureなどと組み合わせて使います。

(require '[clojure.core.async :refer [chan <!! >!!]])

(let [c (chan)
      r (future (<!! c))]
  (>!! c "hello")
  (assert (= @r "hello")))

上記のコードでは、futureで非同期にチャネルから値を読み込み、チャネルに値を書き込んだ後、futureから値を取得します。

timeoutは指定した時間の後閉じられるチャネルを返します。

(require '[clojure.core.async :refer [timeout <!!]])

(assert (= (<!! (timeout 1000)) nil))

実行すると1秒後に値が返ります。

チャネルの選択

alts!!は複数のチャネルの内、書き込みが行われたチャネルと値のペアを返します。

(require '[clojure.core.async :refer [chan >!! alts!!]])

(let [c1 (chan)
      c2 (chan)]
  (future (>!! c1 :foo)
          (>!! c2 :bar))
  (assert (= (alts!! [c1 c2]) [:foo c1]))
  (assert (= (alts!! [c1 c2]) [:bar c2])))

まずc1:fooを書き込まれます。

ここでc1に書き込まれたのでalts!!:fooc1を返します。

次に値が読み出されたことでc2:barが書き込まれます。

ここでc2に書き込まれたのでalts!!:barc2を返します。

alt!!は複数のチャネルの操作から一つ選択します。

(require '[clojure.core.async :refer [chan >!! alt!!]])

(let [c1 (chan)
      c2 (chan)]
  (future (>!! c1 :foo)
          (>!! c2 :bar))
  (assert (= (alt!! c1 ([v c] [v c])
                    c2 ([v c] [v c]))
             [:foo c1]))
  (assert (= (alt!! c1 ([v c] [v c])
                    c2 ([v c] [v c]))
             [:bar c2])))

この例はalts!!と同じ動作をします。

これらの関数はtimeoutと組み合わせることでタイムアウト処理を簡単に記述することができます。

(require '[clojure.core.async :refer [timeout alt!!]])

(assert (= (alt!! (timeout 10000) ([] :foo)
                  (timeout 100) ([] :bar))
           :bar))

10000ms後に閉じられるチャネルと100ms後に閉じられるチャネルでは、後者の方が早くに閉じられます。

go

goはcore.asyncが提供する非同期実行の仕組みです。

goの内側では>!!<!!の代わりに, >!<!を用います。

(require '[clojure.core.async :refer [chan <! >! go]])

(let [c (chan)]
  (go (loop [n 0]
        (>! c n)
        (recur (inc n))))
  (go (assert (= (<! c) 0))
      (assert (= (<! c) 1))
      (assert (= (<! c) 2))))

チャネルから値を読み出す毎に値がインクリメントされていることが確認できます。

go<!>!の呼び出しを見つけ出し、SSA形式のステートマシンを生成します。

継続はcore.asyncのスレッドプールから生成されるスレッドで処理されます。

algo.generic

Clojure標準の関数を総称的な関数として提供するライブラリです。

Generic functions

algo.genericの総称的な関数を示します。

次は四則演算の例です。

(refer-clojure :exclude [+ - * /])
(require '[clojure.algo.generic.arithmetic :refer (+ - * /)])

(assert (= (+ 1 2) 3))
(assert (= (- 1 2) -1))
(assert (= (* 1 2) 2))
(assert (= (/ 1 2) 1/2))

java.lang.Numberに対してclojure.coreの関数と変わりない動作をします。

algo.generic.arithmeticにはzero-typeone-typeが存在します。

(refer-clojure :exclude [+ - * /])
(require '[clojure.algo.generic.arithmetic :refer (+ - * / zero one)])

(assert (= (+ 2 zero) 2))
(assert (= (- 3 zero) 3))
(assert (= (* 5 one) 5))
(assert (= (/ 7 one) 7))
(assert (= (+) zero))
(assert (= (*) one))

zeroは加算における単位元、oneは乗算における単位元として働いています。

これらの関数はMultimethodとして提供されておりユーザーが拡張することが可能です。

複素数とそれに対する演算を定義します。

(ns complex
  (:refer-clojure :exclude [+ - * /])
  (:require [clojure.algo.generic.arithmetic :refer (+ - * / zero one)]))

(defrecord Complex [re im])

(defmethod + [Complex Number] [c n] (+ c (Complex. n 0)))
(defmethod + [Number Complex] [n c] (+ (Complex. n 0) c))
(defmethod + [Complex Complex] [c c']
  (Complex. (+ (.re c) (.re c')) (+ (.im c) (.im c'))))

(assert (= (+ (Complex. 1 1) (Complex. 1 1)) (Complex. 2 2)))
(assert (= (+ (Complex. 1 1) 1) (Complex. 2 1)))
(assert (= (+ (Complex. 1 1) zero) (Complex. 1 1)))

(defmethod - [Complex Number] [c n] (- c (Complex. n 0)))
(defmethod - [Number Complex] [n c] (- (Complex. n 0) c))
(defmethod - [Complex Complex] [c c']
  (Complex. (- (.re c) (.re c')) (- (.im c) (.im c'))))

(assert (= (- (Complex. 1 1) (Complex. 1 1)) (Complex. 0 0)))
(assert (= (- (Complex. 1 1) 1) (Complex. 0 1)))
(assert (= (- (Complex. 1 1) zero) (Complex. 1 1)))

(defmethod * [Complex Number] [c n] (* c (Complex. n 0)))
(defmethod * [Number Complex] [n c] (* (Complex. n 0) c))
(defmethod * [Complex Complex] [c c']
  (Complex. (- (* (.re c) (.re c')) (* (.im c) (.im c')))
            (+ (* (.re c) (.im c')) (* (.im c) (.re c')))))

(assert (= (* (Complex. 1 1) (Complex. 1 1)) (Complex. 0 2)))
(assert (= (* (Complex. 1 1) 0) (Complex. 0 0)))
(assert (= (* (Complex. 1 1) one) (Complex. 1 1)))

(defmethod / [Complex Number] [c n] (/ c (Complex. n 0)))
(defmethod / [Number Complex] [n c] (/ (Complex. n 0) c))
(defmethod / [Complex Complex] [c c']
  (let [d (+ (* (.re c') (.re c')) (* (.im c') (.im c')))]
    (Complex. (/ (+ (* (.re c) (.re c')) (* (.im c) (.im c'))) d)
              (/ (- (* (.im c) (.re c')) (* (.re c) (.im c'))) d))))

(assert (= (/ (Complex. 1 1) (Complex. 1 1)) (Complex. 1 0)))
(assert (= (/ (Complex. 1 1) (Complex. 0 1)) (Complex. 1 -1)))
(assert (= (/ (Complex. 1 1) one) (Complex. 1 1)))

+, -, *, /が複素数に対して使えるようになりました。

総称的な関数を使った関数もまた総称的なものになります。

(refer-clojure :exclude [*])
(require '[clojure.algo.generic.arithmetic :refer (*)])
(require '[complex :refer (->Complex)])

(defn square [x] (* x x))

(assert (= (square 3) 9))
(assert (= (square (->Complex 1 -1)) (->Complex 0 -2)))

四則演算だけでなく、モジュール毎に総称的な関数が定義されています。

core.logic

Clojureで論理プログラミングをするためのライブラリです。