七 月
2
月曜日

core.asyncのalts!とalt!

Clojure core.asyncalts!alt!
名前、機能共に似ており違いや使い方をよく失念するのでメモ。


alts! / alt! は指定した複数のチャネル操作のうちいずれか一つを実行する。

例えば、

  • 複数の入力用チャネルのうち読み込み可能な状態のいずれかのチャネルからデータを読み込む
  • タイムアウト用チャネルを併用しチャネルの読み書き操作にタイムアウトを設ける

といった使い方ができる。

alts!とalt!は大まかに以下の違いがある。

  • alts! は関数。操作結果と操作が行われたチャネルが返る。
  • alt!はマクロ。 複数のチャネル操作とその結果を処理する式を対で定義。式の結果が返る。

alts!について

チャネル操作を表すvectorを引数に取り、操作結果とチャネルが含まれるvectorを戻り値として返す関数。

(alts! ports & {:as opts})

引数

第一引数 ports複数のチャネル操作を表すvector
チャネル操作 とは 読み込み 書き込み のいずれかで、チャネル単体を指定すると 読み込み 、vectorで [チャネル 書き込む値] と指定すると 書き込み を表す。

;; チャネル c1 / c2 / c3 のいずれかから値を読み取る
(alts! [c1 c2 c3])

;; チャネルc1から値を読み取る
;; 1秒以内に読み取りができなければタイムアウトさせる
(alts! [c1 (timeout 1000)])

;; チャネルc2に値 :foo を書き込む
;; 1秒以内に書き込みができなければタイムアウトさせる
(alts! [[c1 :foo] (timeout 1000)])

第二引数以降はオプションを表す。

戻り値

vectorで 操作結果操作が行われたチャネル が返る。

[操作結果 チャネル]

操作結果は操作に応じて以下のパターンがある。

操作 結果 説明
読み込み 非nil チャネルから読み込んだ値
読み込み nil 読み込み対象チャネルが既に閉じていた場合
timeoutチャネルによるタイムアウトが発生した場合
書き込み true 読み込みが成功した場合
書き込み false 読み込み対象チャネルが既に閉じていた場合

オプション

:default :priority の二つのオプションが指定可能。

:default

通常、全てのチャネルが操作可能な状態では無い場合、goマクロによるステートマシンは一時停止状態(parked)になるが :default オプションを指定すると一時停止せず指定した既定値とチャネル(キーワード:default)が返る。

;; チャネル c1 / c2 / c3 のいずれかから値を読み取る
;; いずれのチャネルも読み込み可能でなければ操作結果に :hoge を返す
(alts! [c1 c2 c3] :default :hoge)
;;=> [:hoge :default]

:priority

通常、各チャネルが操作可能かどうかの判定順序は第一引数 ports で指定したチャネルのリストを一旦ランダムに並び替えた後の順序で行うが、:priority オプションに true を指定すると ports で指定した順に行われる。

;; チャネル c1 / c2 / c3 のいずれかから値を読み取る
;; c1 c2 c3の優先順位で判定する
(alts! [c1 c2 c3] :priority true)

alt!について

複数のチャネル操作とその結果に適用する式を対で定義したalt節を引数で受け、式の結果を戻り値として返すマクロ。

(alt! & clauses)

引数

複数のチャネル操作を表すvector を対とするalt節を引数として受け取る。

チャネル操作は alts! の引数 ports と同様に 読み込み 書き込み のいずれかで、チャネルを指定すると 読み込み 、vectorで [チャネル 書き込む値] と指定すると 書き込み を表す。

式の第一要素はvectorで [操作結果] または [操作結果 チャネル] で操作結果をパラメータとして受け取ることができる。
受け取るパラメータの内容は alts! の戻り値と同じ。

;; チャネル c1 / c2 のいずれかから値を読み取る
(alt! [c1] ([v] (prn "c1から値を取得 " v) v)
      [c2] ([v] (prn "c2から値を取得 " v) v))

;; チャネルが一つの時はvectorで表現しなくても良い
(alt! c1 ([v] (prn "c1から値を取得 " v) v)
      c2 ([v] (prn "c2から値を取得 " v) v))

;; alts!のように複数の読み込み操作を一度に指定できる
(alt! [c1 c2] ([v] (prn "c1またはc2から値を取得 " v) v)
      c3      ([v] (prn "c2から値を取得 " v) v))

;; チャネルc1から値を読み取る
;; 1秒以内に読み取りができなければタイムアウトさせる
(alt! c1             ([v] (prn "c1から値を取得 " v) v)
      (timeout 1000) ([_] (prn "タイムアウトしました") :timeout))
      
;; チャネルc2に値 :foo を書き込む
;; 1秒以内に書き込みができなければタイムアウトさせる
;; 書き込み操作が一つだとしてもvectorは省略できない。 [[c2 :foo]] とネストして表現する。
(alt! [[c2 :foo]]    ([v ch] (prn "c2への書き込み結果 " v) :ok)
      (timeout 1000) ([_] (prn "タイムアウトしました") :timeout))

;; alts!のように複数の書き込み操作を一度に指定できる
(alt! [[c1 :foo] [c2 :bar]] ([v ch] (prn "c1 または c2への書き込み結果 " v) :ok)
      (timeout 1000)        ([_] (prn "タイムアウトしました") :timeout))

書き込み操作が一つだとしてもvectorは省略できない 点に注意。

読み込みが一つの場合はvectorを省略できるきため、

(alt! c1 ([_] ... ))

と書けるので、書き込みの場合も

(alt! [c1 :hoge] ([_] ... ))

とは書きたくなってしまうがこれはNG。
上記はvectorで指定された複数のチャネル(c1 :hoge)いずれかの読み込み操作と解釈されエラーとなる。

書き込み操作は一つだとしても常にネストしたvectorで表現する。

(alt! [[c1 :hoge]] ([_] ... ))

戻り値

式の結果が戻り値として返る。

オプション

alts! と同じく :default :priority の二つのオプションが指定可能。

以下ようにalt節に混ぜて利用する。

;; チャネル c1 / c2 のいずれかから値を読み取る
;; いずれのチャネルも読み込み可能でなければ操作結果に :hoge を返す
(alt! [c1] ([v] (prn "c1から値を取得 " v) v)
      [c2] ([v] (prn "c2から値を取得 " v) v)
      :default :hoge)
      
;; チャネル c1 / c2のいずれかから値を読み取る
;; c1 c2の優先順位で判定する
(alt! [c1] ([v] (prn "c1から値を取得 " v) v)
      [c2] ([v] (prn "c2から値を取得 " v) v)
      :priority true)