[#UEFN][#Verse]UEFNのカスタムイベント実装を考える[4] 「Event:event() = event(){}」を考える
前回はこちら
「event(){}」ってなんだ
前回の記事において、重要な説明がすっぽり抜けていた事に気づきました。
Event<public>:event() = event(){}
event
クラスのインスタンスを定義するこのコードなのですが、良く見ると右辺がevent(){}
と、アーキタイプ{}
の前に丸括弧()
がついてまして、クラスインスタンス生成の記述として違和感があります。更に言えば、インスタンスを格納するEvent
定数の型もevent()
となっていて、型を記述するべき箇所がまるで関数呼び出しのように読めます[1]。
この式が具体的に何をしているのかを読み解くには、Verseの複数の特徴的な言語仕様を把握する必要があります。正直知らなくても困らないんですが、せっかく調べたので解説したいと思います。
実を言いますと、前の記事は少し前にひな形を書いていて、後は上記について調べれば良いとなったところで本業が忙しくなって執筆を中断していました。ようやく記事を書ける余裕が出来たので、以前書いたテキストを整形してアップしたんですが、この事をすっかり忘れていたのでした(すみません)。
というわけで今回は前回の補足になります。
1:eventクラスのおさらい
まずevent
クラスについておさらいしましょう。event
クラスはパラメータ型クラスです。なので、本来はevent(t)
クラスと書いた方がいいかもしれません[2]。
event
クラスはVerse.digest.verse
ファイル内でAPIが定義されています(nativeコードなので実装は読めません)
event<native><public>(t:type)<computes> := class(signalable(t), awaitable(t)):
Await<native><override>()<suspends>:t
Signal<native><override>(Val:t):void
t
がパラメータ型です。後述しますが、このt
はeventオブジェクトが取り回す「ペイロード(payload. [3])」の型になります。
ペイロードについては次回以降説明する予定で、今回は「このペイロードを使わないためにVerseが採用しているテクニック」についての話になります。
Await()
はawaitable
インターフェイス、Signal()
はsignable
インターフェイスでそれぞれ宣言されているメソッドで、それぞれタスクの待機状態を制御します(詳しくは前回の記事を参照)。
2:パラメータ型クラス
パラメータ型クラスは、インスタンスを生成する時に下の様なパラメータ型を設定する必要があります。
DummyConstant1:= event(int){}
ここではパラメータにint
型を設定したevent
クラスのインスタンスを生成し、DummyConstant1
定数として定義しています。行末の{}
は空のアーキタイプで、インスタンスの生成処理を示しています。
DummyConstant1
定数は型推論出来るので型宣言が省略されています。省略せずに書くと以下の様になります。
DummyConstant1:event(int) = event(int){}
event(int)
の括弧内にあるint
は、値ではなく型だという点に注意してください[4]。
3:型を返す関数
パラメータ型クラスにパラメータを設定する事を、ここでは「パラメタライズする」と呼ぶ事にします[5]。
さて、「パラメタライズされたクラス」も「クラス」、つまり型です。なので、「パラメタライズされたクラスの型」を返す関数を書けます。
DummyFunction1() := event(int)
先程はDummyConstant1
定数を定義しましたが、こちらはDummyFunction1()
関数を定義しています。
前述した通り、event(int)
は関数呼び出しではなく、「パラメタライズされたクラスの型」を表します。先程と異なり、末尾にアーキタイプ{}
がありません。つまりこの関数は「event(int)
型のインスタンス」を返すのではなく「event(int)
型」を返す関数なのです。
DummyFunction1()
関数も型推論出来るので型宣言が省略されています。省略せずに書くと以下の様になります[6]。
DummyFunction1():type{event(int)} = event(int)
このようになっていると、型を返している事が分かりやすくなるかと思います。
4:event()ヘルパー関数
2と3を踏まえた上で、話を進めます。
Verse.digest.verse
ファイルには、event
クラスとは別個に、ヘルパー関数event()
が下記のように定義されています。
event<public>()<computes> := event(tuple())
省略された型宣言を補完しておくと以下の様になります[7]。
event<public>()<computes> :type{event(tuple())}= event(tuple())
VSCodeでインターフェイスを確認すると若干違うんですが、これは(signalable(t), awaitable(t))
への対応を示しているのだと思います(多分。自信無し)。
event()
関数の実装であるevent(tuple())
は「パラメタライズされたクラスの型」です。繰り返しになりますが、event(tuple())
は関数呼び出しでもなければ[8]、クラスインスタンスの生成でもない[9]点にご注意ください。また、パラメータとして引数に設定されているtuple()
は「要素数ゼロのtuple
の型」を表します(関数呼び出しではありません!)。
さて、「要素数ゼロのtuple
の型」とは詰まるところ「値を持たない(持てない)型」を意味します。何故そんな値でパラメタライズするのでしょうか?
最初にも説明しましたが、event
クラスはペイロードを管理する機能を持っていて、設定するパラメータはそのペイロードの型を表します。しかし、ペイロードが不要なイベント処理も頻繁に使うので、その際にいちいち不要なパラメタライズを記述するのは面倒です。
そこで、予めペイロードとして「要素数ゼロのtuple
の型」を設定した(つまり、ペイロードを使わない)event
オブジェクトを生成できるように、ヘルパー関数event()
が用意されているのです。
5:Eventオブジェクトの定義
長々と書いてきましたが、これでようやく冒頭のコードを読めるようになりました。
Event<public>:event() = event(){}
event()
は4で定義したヘルパー関数の呼び出しです。event()
関数は「パラメタライズされたクラスの型」であるtype{event(tuple())}
を返します。型を返すという事は、型を記述する位置にこの関数を記述できる事を意味します。ここでは、型定義の位置と、アーキタイプ{}
の手前とにそれぞれ記述しています。
つまり、このコードは以下の様にマクロ展開されると考える事が出来ます[10]。
Event<public>:event(tuple()) = event(tuple()){}
ここでのevent(tuple())
は(くどいようですが関数呼び出しではなく)「パラメタライズされたクラスの型」です。tuple()
は「要素数ゼロのtuple
の型」なので、Event
定数はペイロードを持たないevent
クラスのインスタンスとして定義されます。
ちなみに、VSCodeでインターフェイスを確認するとやっぱり若干違うんですが、これも(signalable(t), awaitable(t))
への対応を示しているのだと思います(多分。自信無し)。
以上で前回記事の補足は終わりです。お疲れ様でした!
終わりに&「ペイロード」に続く
理屈は分かったのですが、「ペイロードを持たないevent
クラス」を生成するために、わざわざ恐ろしく回り道をしているように感じるのは気のせいでしょうか……?
さて、今回は広大な迂回路を通ることで存在を隠蔽した「ペイロード」ですが、逆にこれを使うと何が出来るのでしょうか? 次回はこれを考えてみることにします。
続き
お知らせ
verse言語とUEFNの記事を他にも書いているので御覧下さい。
最後まで読んで頂きありがとうございました。この記事がお役に立てたようであれば、是非LIKEとフォローをお願いします(今後の執筆のモチベーションに繋がります)。
#Verse #UEFN #Fortnite #Verselang #UnrealEngine
-
後述しますがこれは実際に関数呼び出しです ↩︎
-
個人的には
event(payload)
とした方が分かりやすいと思いますが、ここでは公式リファレンスに準じています。 ↩︎ -
語義は飛行機なので運ぶ荷物の事。プログラミング用語しては転送するデータ列くらいの意味 ↩︎
-
ただ、verseでは型は値に含まれる(first value)らしいのでややこしい ↩︎
-
こういう表現が本当にあるのかどうか知らない ↩︎
-
ただしこれはイメージで、実際にこのように展開すると、後述する
event()
ヘルパー関数と曖昧さが発生してコンパイルエラーになります ↩︎ -
同様にイメージです ↩︎
-
関数呼び出しの場合、引数を1個持つevent()関数を呼びだしている事になるが、そんな関数は宣言されていない ↩︎
-
インスタンス定義の場合は末尾にアーキタイプ
{}
が必要 ↩︎ -
同様にイメージです ↩︎
Discussion