前回記事の補足です。さきにこちらを読んだ方がわかりやすいかも。
前回記事はこちら。
someiyoshino.info
R3のイベントストリームを生成する時は大抵こういうコードを書きます(念のために言っておくと、実際にはこの一行だけを書く事は無いと思います)。
var updateStream = Observable.EveryUpdate();
このコードは、"var"による型推論を使わない場合は以下のようになります。
Observable<Unit> updateStream = Observable.EveryUpdate();
Observable<T>のジェネリック型に指定されているUnit型とはなんなのかというのが今回の記事になります。
注意
以下、あくまで土屋の解釈なので、実装者の意図と異なる場合があります。予めご了承ください。
イベント処理時に伝播されるパラメータ
先に、イベント発生・処理時に伝播するパラメータについて考えてみます。
イベントリスナーモデル(≒Observeパターン)では、あるイベントが発生すると、予め登録してあったメソッドが実行されます。この「イベントの発生を検出し、対応するメソッドを実行する」というのは単なる処理フローであって、「イベント」それ自体に相当するオブジェクト(≒値≒パラメータ)は用意されません(少なくとも概念レベルでは)。
例えば、マウスクリックイベントが発生した時にリスナーとして登録していた関数が実行されたとして、特に受け取るべきパラメータは無いわけです。一方、パラメータの伝播が必要なイベントもあります。例えば、キーボードのキー押下イベントでは、どのキーが押されたのかのキャラコードを受け取る必要があります。
このように、イベントによってパラメータを必要としない物と、そうで無い物があるわけです。
Observable<T>のジェネリック型には、このイベント発生時に伝播するパラメータを指定します。しかし、パラメータが無い場合にvoidを指定することはできません。voidは型ではないので、ジェネリック型として使えないのです。
そこで用意されているのがUnit型というわけです。
Unit型
「Unit型」と言うシンプルな名前から、C#の組み込み型っぽい印象があるかもしれませんが(そうでもない?)、この型はR3で実装されているカスタムの構造体です。Unit型自体はScalaやF#などの関数型言語で採用されていて、R3.Unitはそれらを踏襲した物です。
R3.Unit型は、「値がない事を示す」ために用意されている型です。Unit型自体は機能を持たず、また全てのインスタンスは同一と見なされます*1。
さて、Unit型は値を持ちませんが「型」ではあるので、ジェネリック型として指定できます*2。この仕組みによって、値を伝播しないイベントと、値を伝播するイベントを、どちらもObservable<T>という統一記法で表せるわけです。
R3のユーザーがObservable<Unit>
と記述することは基本的に無いと思いますが*3、#1のサンプルコードのように、処理を細かく確認していくとUnit型は頻出するので、覚えておくと良いかなと思います。
ちなみに、"Unit"という名前は集合論における単位集合(Unit set. 要素数1の集合のこと)に由来しています。F#/Scalaでは、Unit型は常に"()"という特殊なトークン*4を返します。なお、R3.Unit.toString()も"()"を返します。
おまけ
これまでイベントが発生することを「イベントの発火」と呼んでいました。ただ、これだと発生タイミングと処理タイミングを上手く呼び分けられないと思い、ちょっと調べたら、そもそもこの訳語(?)は適切で無いという記事を見つけました。
note.com
なるほど感。