ジャンプ#

ラベルとリターン#

式の要素にラベルを付けて、そのラベルへ明示的に値を返しつつ、処理を早期に脱出できます。

簡単な例#

ラベル演算子 formula !: label で式 formula に対してラベルを付与できます。

formula 内では、リターン演算子 label!! value でそのラベルに対して値を返すことができます。

$ xa '
  (
    label!! 10
    20
  ) !: label
'
# 10

ラベルは変数とは異なるテーブルで管理されており、リターン演算子の左辺からのみ参照が可能です。


ラベル演算子 formula !: label は非常に独特の見た目を持っていますが、これはエルビス演算子 value ?: default との類似性を意識したものです。

これらはともに左辺に特別な状態を取る可能性があり、それを右辺で受け取ります。

ただし、ラベル演算子は結合優先度が低く、 formula にはパイプ | や右実行パイプ >> を含めることができます。


リターン演算子 label!! value は、 labelreturn のような文字列の場合に 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 で行います。

スローする値はどのようなタイプの値であっても構いません。

スローした値はキャッチ演算子 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!

ストリームの解決#

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 !? catchtry には単一の関数呼び出しや演算子が来る場合が多いためです。

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 !? catchtry には、論理演算子までを含めることができ、パイプ系演算子を含めることはできません。

パイプ系演算子を含めるためには丸括弧で囲う必要があります。