LoginSignup
2
1

【UEFN】VerseでObservableなデータ型を考える

Posted at

Observableとは

Observableとはリアクティブプログラミングにおける概念でデータをストリームとして扱い変更やイベントを検知するための仕組みです。

リアクティブプログラミングについては別の方の解説を見ていただくのが良いと思うので僕の方からはVerseにおけるリアクティブプログラミングのアプローチについて触れられたらと思ってます。

今回実装するObservableの要件

  • 変数の変更、取得が可能なこと
  • 値の変化時に検知が可能なこと

を満たす形をまずは目指します(超最低限)

Observableクラスの生成

int型をObservableクラスに変換する例に挑戦します。
今回のObservableクラスは一つの値を持ちその変化をストリームとして扱うようなイメージで実装します。

IntObservable := class:
    var Value<private>: int = 0
    Get(): int = Value
    Set(v: int): void =
      set Value = v

(Input: int).Obs(): IntObservable =
    Obs :IntObservable = IntObservable{}
    Obs.Set(Input)
    return Obs
    
Sample(): void =
    V :int = 1
    VObservable := V.Obs()

この処理によりint型の変数をIntObservable関数に変換できます。
IntObservable型はint型のValueを値として持ちこの変数はprivateを指定しているためクラスの初期化時に値を渡せないようにしています。これは値の変更は常に副作用が存在する可能性があり、クラスの外から値の変更を検知できない場合Observableが機能しなくなるからです。

上記の実装のIntObservableはSet/Getの関数を持ちそれぞれ値の設定及び取得ができるようになっています

値の変化時に検知する

IntObservaleのValueが変化したときを検知し、そのタイミングで処理を行いたいときにイベントを発火させる処理を書いていきましょう

先程のIntObservableクラスに以下の処理を追記しましょう

  IntObservable<public> := class():
    var Value<private>: int = 0
    var Listeners<private>: [int]?type{_(:int): void} = map {}
    var ListenerIndex<private> :int = 0

    // Get / Setは省略します
    
    Subscribe(f: type{_(:int): void}): int =
      option { set Listeners[ListenerIndex]  = option{ f } }
      set ListenerIndex += 1
      ListenerIndex

    Unsubscribe(_ListenerIndex: int): void =
      option { set Listeners[_ListenerIndex] = false }  

Subscribe関数は引数に関数を渡します。
渡された関数はValueの変化時に更新を通知するために呼び出します。

値の変化は任意のタイミングで起こるためSubscribeされた関数はメンバ変数に保存します(Listeners)

Unsubscribe関数はSubscribe関数の返り値であるint型を引数でうけとり、すでにSubscribe済みのコールバック関数を削除するために利用します

値の変化時にコールバック関数を呼び出す

最後に値の変更時に設定されたコールバック関数を呼び出す処理を書きます

  IntObservable<public> := class():
    var Value<private>: int = 0
    var Listeners<private>: [int]?type{_(:int): void} = map {}
    var ListenerIndex<private> :int = 0

    // 省略

    Set(v: int): void =
      set Value = v
      Listeners.ForEach(Callback)

    // 省略

  (Input:[int]t where t:type).ForEach(predicate: type{_(:t): void}): void = 
    for(Key -> V : Input):
      predicate(V)

やり方はシンプルでListenersに保存している関数をすべてSet関数が呼び出されたタイミングで実行しているだけです。

まとめ

今回はint型をObservable型として扱うシンプルなアプローチを紹介しました。
リアクティブプログラミングにはまだまだ機能不十分ですが一つのアプローチとして参考にしていただけると幸いです。

int型専用のObservable型にしないと行けなかった理由もあるのですがそれに関しては別の記事で紹介できればと思います。

  IntObservable<public> := class():
    var Value<private>: int = 0
    var Listeners<private>: [int]?type{_(:int): void} = map {}
    var ListenerIndex<private> :int = 0

    Get(): int = Value
    Set(v: int): void =
      set Value = v
      Listeners.ForEach(Callback)

    Callback(f: ?type{_(:int): void}): void = 
      if(F := f?) { F(Value) }

    Subscribe(f: type{_(:int): void}): void =
      option { set Listeners[ListenerIndex]  = option{ f } }
      set ListenerIndex += 1
      ListenerIndex

    Unsubscribe(_ListenerIndex: int): void =
      option { set Listeners[_ListenerIndex] = false }    

  (Input: int).Obs(): IntObservable =
    Obs :IntObservable = IntObservable{}
    Obs.Set(Input)
    return Obs
  
  (Input:[int]t where t:type).ForEach(predicate: type{_(:t): void}): void = 
    for(Key -> V : Input):
      predicate(V)
2
1
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
2
1