土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

#UEFN #Verse v28.10から、set式の右辺に失敗許容式を記述すると警告が出るようになった件についての個人的な考察

近況報告

 年末から2月いっぱいくらいまで非常に忙しく、また連続して体調を崩していた事もあり、UEFNどころかFortniteを起動する余裕もありませんでした。すっかり浦島太郎状態ですが、ゆるゆるとUEFNの活動も再開したいと思っています。ただ、Unityの活動も再開したのでペースは以前より落ちる筈なのでご容赦ください。

 また、UEFN/Verseについては今までブログを分離してzennで書いていたんですが、管理が面倒なので今後ははてなブログに統一します。できるだけタグとかで上手く識別できるといいな(希望)。

set=の仕様が変わった話

 少し前に話題になっていましたが、v28.10以降、set=の右オペランドに失敗許容式を記述すると、以下の様な警告が表示されるようになりました。

 余談ですが、set=は「変数代入(Variable assignment)演算子」にあたり、=の左右にそれぞれオペランドを取ります(Verseにはこのような解釈をする演算子が幾つかあります)。

 エラーメッセージは以下の通り。末尾の"(2011)"はエラー番号なので無視します。

This expression in the right operand of 'set ... = ...' can 
fail, but the meaning of failure here will change in a future 
version of Verse. To preserve the current meaning of this code 
in future versions of Verse, you should move the expression 
that can fail outside the 'set'.
For example, if you have the expression:
    set Variable = ExpressionThatMightFail[],
you can change it to the following two expressions to preserve 
the meaning:
    Value := ExpressionThatMightFail[]
    set Variable = Value

(私訳)set ... = ...の右オペランドにあるこの式は失敗可能です。しかし、ここでの失敗の意図はVerseの将来バージョンで変更されます。このコードの現在の意図をVerseの将来バージョンで維持するために、失敗可能な式をset=の外に移動する必要があります。

たとえば、このような式の場合:

set Variable = ExpressionThatMightFail[],

意図を維持するために、これを以下の様な2つの式に変えることができます。

Value := ExpressionThatMightFail[]
set Variable = Value

 文章を素直に受け取ると「set=演算子の右オペランドが失敗した時の挙動が将来的に変わる予定なので、その時バグらないように右オペランドに失敗許容式を書かないようにしましょう」という事になります。

 というわけで、さきほどのコードも2行に分ける事でwarningが解除されました。

Verse言語に導入される予定の言語仕様バージョンについて

 この警告について、公式スタッフがフォーラムでも言及しています。 forums.unrealengine.com  全文訳出するのは大変なので以下要点をまとめました(厳密な説明については原文を参照してください)。

  • v28.10からVerseには言語仕様についてバージョンが導入された。ただし、現時点ではバージョンゼロしか無い(つまり、バージョン違いは存在しない)。
  • 今後、破壊的変更がある場合にバージョンが上がる。
  • すでに、将来的に破壊的変更が予定されている仕様が2個ある。
  • それらはバージョンゼロでは非推奨(Dpuricated)扱いになり、警告が表示される。
  • バージョンゼロ環境下ではこれらの警告は無視して構わないが、警告が出ないように修正しておけば、将来的にバージョンアップした際にシームレスに移行出来る。

 今後予定されている破壊的変更は以下の2点です。

  1. Failure of the right-hand subexpression of a set e0 = e1 expression.(set e0 = e1式の右側サブ式の失敗(の扱い))
  2. Failure of a key subexpression in a map literal expression.(mapのリテラル式でkeyサブ式の失敗(の扱い))

 1が今回のケースにあたります(2は近いうちに軽く記事にする予定)。

 現在の仕様では、どちらも式が失敗した時、その失敗は外側の失敗コンテキストに伝播します。将来的にこの振る舞いが変更されるので、その時に意図しない挙動にならないように、ひとまず該当箇所に失敗許容式を記述しないようにしておくべきという事のようです。

 このエントリには「新仕様でどうなるのか」について説明が無いので歯がゆいのですが、ひとまず言われたとおりにコードを修正しておくのが安全だと思われます。

この仕様変更は何を目的としているのか(個人的な推測)

 さて、「set=の仕様が変わるのは良いとして、その変更はなにを目的としているのか?」という事について考えてみます。以下は根拠の無い個人的な推測なのでその旨ご理解ください。

 mapに値を設定する時にset=を使いますが、mapの要素へのアクセスは失敗許容式になるため、このset=は失敗コンテキスト内でしか記述できません。

 しかし、以前記事にも書いた通り、この仕様は奇妙です。

zenn.dev

 右オペランドに記述したmap要素への参照が失敗することは考えにくく、常に失敗コンテキストを用意しなければならないのはおかしな話で、正直言語仕様上のバグに思えます。

 というわけで、この仕様バグに対処するために、set=に破壊的変更が入るのではないかと予想しています(あくまで個人的な予想)。

 具体的には、set=の内側を失敗コンテキストとします。失敗コンテキストの中では失敗許容式を記述出来るので、左オペランドにmap[key]を記述できます。

 set=の内側で失敗が起きた場合、それはset=の失敗コンテキストが受け、外には伝播しません。これにより、set=が他の失敗コンテキスト内に記述する必要がなくなります。

 この場合、右オペランドに失敗許容式が記述された際の挙動が、旧仕様と新仕様で以下のように変化します(set=が失敗コンテキスト内で記述されているかどうかで挙動が変わります)。

例:set mapX[key] = funcA[]というコードで、funcA[]が失敗した場合の挙動

  • 旧仕様:
    • 失敗コンテキスト内:代入は実行されない(失敗は外側に伝播する)
    • 失敗コンテキスト外:書けない(コンパイルエラー)
  • 新仕様:
    • 失敗コンテキスト内:代入は実行されない(失敗は外側に伝播しない)
    • 失敗コンテキスト外:代入は実行されない(失敗は外側に伝播しない)

 恐らく、こういうことなんじゃないかなあ……? 「mapに値を設定する」という処理はVerseで多用されるので、変な制約は外してしまいたいのかなーと(というかそうであって欲しい)。

追記:mapの初期化構文についての破壊的変更

 言語仕様変更が予定されているもう一点についても解説しておきます。  現在、mapの初期化構文では、以下の様にキーに失敗許容式を記述できます。

MyMap := map{FallibleCall[] => Value}

 verseでは将来的にラムダ式(≒匿名関数)の採用が予定されています。恐らく、ラムダ式はこんな感じのコードになります(記法は想像です)。

#関数は`SampleFunc(F(:int):int)`として定義されているとする
SampleFunc( x => x * 2)

 =>がラムダ式を表す演算子で、左辺がインターフェイス、右辺が引数、左辺が引数を使った式になります。  左辺はインターフェイスの宣言なので、動的な式は記述できません。これは、現状のmapの初期化構文の仕様と矛盾します。なので、この記法は将来的に使え無くなるのです(多分)。  現在のコードを将来的にも機能させたい場合は、以下の様にコードを書き直せばOKです。

Key := FallibleCall[]
MyMap := map{Key=>Value}

 mapの初期化構文だけ特別扱いしてもいいんじゃないかと思うのですが、mapの値にラムダ式を記述したい場合とかに困るのかもしれません。

宣伝

Verse言語の言語仕様解説同人誌をPDFで頒布しています。よろしければどうぞ。

s-games.booth.pm