ジャンプ#
ラベルとリターン#
式の要素にラベルを付けて、そのラベルへ明示的に値を返しつつ、処理を早期に脱出できます。
簡単な例#
ラベル演算子formula !: labelで式formulaに対してラベルを付与できます。
formula内では、リターン演算子label!! valueでそのラベルに対して値を返すことができます。
$ xa '
(
label!! 10
20
) !: label
'
# 10
ラベルは変数とは異なるテーブルで管理されており、リターン演算子の左辺からのみ参照が可能です。
ラベル演算子formula !: labelは非常に独特の見た目を持っていますが、これはエルビス演算子value ?: defaultとの類似性を意識したものです。
これらはともに左辺に特別な状態を取る可能性があり、それを右辺で受け取ります。
ただし、ラベル演算子は結合優先度が低く、formulaにはパイプ|や右実行パイプ>>を含めることができます。
リターン演算子label!! valueは、labelがreturnのような文字列の場合にreturn!! "Result"のように、まずまずの可読性をもたらします。
演算子!!の見た目はスロー演算子!! valueと対応しており、その挙動も似通っています。
ループからの脱出#
リターンはループからの途中脱出にも利用できます。
以下の例では、1から100までの整数を順番にループしつつ、2でも3でも5でも割り切れる数を探します。
$ xa '
(
1 .. 100 | (
(_ % 2 == 0 && _ % 3 == 0 && _ % 5 == 0) && found!! _
)
NULL
) !: found
'
# 30
ラムダ式の内側からのリターン#
関数に渡すラムダ式の内側から、外側のラベルに対してリターンすることもできます。
$ xa '
forEach := array, block -> array() | *block
(
array := [1 .. 100]
forEach[array] ( _ =>
(_ % 2 == 0 && _ % 3 == 0 && _ % 5 == 0) && found!! _
)
NULL
) !: found
'
# 30
ストリームの副作用#
ラベル演算子のformulaの戻り値がストリームである場合、必ずその場で1度だけ評価され、副作用も1度だけ生じます。
$ xa '
count := 0
stream := (
1 .. 3 | (
count = count + 1
count
)
) !: break
[stream], [stream], [stream], count
'
# [1;2;3]
# [1;2;3]
# [1;2;3]
# 3
これはラベルの戻り値であるストリームが1度も評価されない場合も同様です。
$ xa '
count := 0
stream := (
1 .. 3 | (
count = count + 1
count
)
) !: break
count
'
# 3
この動作の実現のため、式(getter)であるラベルはformulaの戻り値であるストリームの要素列を配列にキャッシュします。
この処理はラベルを文(runner)として実行することで抑制でき、メモリを節約できます。
$ xa '
(
1 .. 1000 | (
[1 .. 1000]
)
) !: break;
'
# NULL
リターン値の省略#
リターン演算子はlabel!!のようにvalue節を省略して書くこともできます。
この場合、NULLが返されます。
$ xa '
(
label!!
20
) !: label
'
# NULL
リターン演算子・ラベル演算子の結合優先度#
リターン演算子はリテラル系演算子の一種として扱われており、value節にはカンマ系演算子およびそれよりも結合優先度の高い任意の式が来れます。
以下の例ではvalue節がカンマ演算子を直接含んでいます。
$ xa '
(
value := 10
value > 5 && result!! "Larger than 5:", value
"Not larger than 5:", value
) !: result
'
# Larger than 5:
# 10
一方、ラベル演算子と共起した場合はリターン演算子は外側のラベル演算子によるラベルを参照できます。
これはラベル演算子の結合優先度がリターン演算子よりも低く、右実行パイプと同じレベルであるためです。
$ xa '
result!! "123" !: result
'
# 123
リターン演算子の左辺には識別子のみを指定できます。
また、リターン演算子に前置演算子を付与した場合、その前置演算子はリターン演算子全体を受け取ります。
以下の例では、関数化演算子によってリターン演算子全体を関数にする例です。
$ xa '
(
fail := \break!! "Failed: $_"
1 == 1 || fail("1 is not equal to 1")
1 == 2 || fail("1 is not equal to 2")
"Success"
) !: break
'
# Failed: 1 is not equal to 2
ラベル演算子!:は右実行パイプ>>と同じレベルの結合優先度を持ちます。
左辺にはパイプ|や右実行パイプ>>を取ることができ、パイプや右実行パイプもまた、左辺にラベル演算子を取ることができます。
以下の例では、ラベル演算子!:がそれより左側のパイプによるループ全体を取るため、リターン!!によってループが途中脱出されます。
そして、右側のパイプはラベル演算子全体を取ることができます。
$ xa '1 .. 100 | (_ %% 3 && _ %% 5) && found!! _ !: found | _ * 10'
# 150
例外機構#
例外機構は、式の戻り値以外の経路によって値を返すための仕組みです。
例外機構は値のスローとキャッチによって構成されます。
スローは任意の型の値で行うことができ、スローされた値(=例外値)がキャッチされるまでの間、式や関数の呼び出し階層を越えて伝搬します。
例外値のキャッチが行われなかった場合、呼び出し階層の最上位であるディスパッチャーが例外を処理します。
例外機構は、処理の「失敗」を表現するためにエラーとして定義された値をスローすることによく使われますが、必ずしもこの用途に限定されるものではありません。
例外機構を単に関数や式の深い呼び出し階層の内側から外側へ値を返すための制御構文として使ってもかまいません。
スロー演算子#
Xarpiteでは、値のスローをスロー演算子!! valueで行います。
スローする値はどのようなタイプの値であっても構いません。
ERROR型の値をスローした場合は、その値そのものではなく、それが表しているネイティブエラーが再スローされます。
スローした値はキャッチ演算子try !? catchの右辺に渡されます。
$ xa '
(
!! 12345
) !? ( e =>
"Error: $e"
)
'
# Error: 12345
スロー値の省略#
スロー演算子は、単に!!のようにvalue節を省略して書くこともできます。
この場合、NULLがスローされます。
$ xa '(!!) !? ( e => "Error: $e" )'
# Error: NULL
キャッチ演算子#
キャッチ演算子try !? catchは、try節で値がスローされなかった場合はtry節の値を、スローされた場合はcatch節の値を返す演算子です。
スローされる値はどのようなタイプの値である可能性もあります。
$ xa '"OK" !? "Failed"'
# OK
$ xa '(!! "Error") !? "Failed"'
# Failed
スローされた値の受け取り#
引数付きキャッチ演算子try !? ( error => catch )により、スローされた値を受け取ることができます。
$ xa '
(
!! "Error, world!"
) !? ( e =>
"ERROR: $e"
)
'
# ERROR: Error, world!
ネイティブエラーの捕捉#
キャッチ演算子は、ホスト言語に由来するネイティブエラーも捕捉します。
捕捉されたネイティブエラーは、ネイティブエラークラスERRORのインスタンスとしてcatch節に渡されます。
$ xa 'ERROR.throwNativeError("boom") !? ( e => e ?= ERROR )'
# TRUE
ストリームの解決#
try節の戻り値がストリームである場合、そのストリームは解決されます。
$ xa '
count := 0
stream := (
1 .. 3 | (
count++
count
)
) !? "ERROR"
[stream], [stream], [stream], count
'
# [1;2;3]
# [1;2;3]
# [1;2;3]
# 3
これにより、キャッチ演算子の戻り値であるストリームが1度も評価されなくても、try節の副作用は必ず1度だけ生じます。
$ xa '
count := 0
stream := (
1 .. 3 | (
count++
count
)
) !? "ERROR"
count
'
# 3
try節のストリームの内部でスローされた場合もキャッチされ、キャッチ演算子の戻り値はcatch節の戻り値となります。
$ xa '
count := 0
stream := (
1 .. 3 | (
_ == 3 && !! "The last element error"
count++
count
)
) !? ( e =>
"ERROR: $e"
)
[stream], count
'
# [ERROR: The last element error]
# 2
文レベルのキャッチ演算子#
キャッチ演算子は式レベルとしても文レベルとしても機能します。
文レベルの位置に書かれたキャッチ演算子は、両辺とも文として解釈しようとします。
文レベルでもtry節のストリームは解決され、副作用も1度だけ生じます。
$ xa '
count := 0
(
1 .. 3 | (
count++
)
) !? "ERROR"
count
'
# 3
スロー演算子・キャッチ演算子の結合優先度#
スロー演算子!! valueはリターン演算子label!! valueと同様の結合規則を持ち、value節にはカンマ系演算子およびそれよりも結合優先度の高い任意の式が来れます。
以下の例ではvalue節がカンマ演算子を直接含んでいます。
$ xa '
(
value := 10
value > 5 && !! "Larger than 5:", value
"Not larger than 5:", value
) !? ( e => e )
'
# Larger than 5:
# 10
一方で、キャッチ演算子try !? catchの結合優先度はエルビス演算子value ?: defaultと同じレベルです。
これは、常にリターン演算子がformula内に書かれるラベル演算子formula !: labelと異なり、実際のキャッチ演算子try !? catchのtryには単一の関数呼び出しや演算子が来る場合が多いためです。
Xarpiteのキャッチ演算子は一般的なプログラミング言語におけるtry-catch文ではなく、むしろ三項演算子の同類とみなした方が理解が早いです。
このため、キャッチ演算子はカンマよりも結合優先度が高いです。
$ xa '
evenOrThrow := x -> x % 2 == 0 || !! "Not even: $x"
x := 5
"Number: $x", evenOrThrow(x) !? ( e => "Caught error: $e" ), "Mod 2: $(x % 2)"
'
# Number: 5
# Caught error: Not even: 5
# Mod 2: 1
キャッチ演算子のtry !? catchのtryには、論理演算子までを含めることができ、パイプ系演算子を含めることはできません。
パイプ系演算子を含めるためには丸括弧で囲う必要があります。
ERROR: ネイティブエラー#
ERRORは、ホスト言語に由来するネイティブエラーを表すクラスです。
値 ?= ERRORにより、値がネイティブエラーであるかどうかを判定できます。
message: エラーメッセージ#
messageプロパティにより、ネイティブエラーのメッセージを取得できます。
メッセージが存在しない場合はNULLになります。
throwNativeError: ネイティブエラーのスロー#
ERROR.throwNativeError(message: STRING): NOTHING
指定したメッセージを持つネイティブエラーをスローします。
$ xa 'ERROR.throwNativeError("Something went wrong") !? ( e => e.message )'
# Something went wrong