Amber

A Language for High-Level Programming with Self-Extension

チュートリアル7: 制御構造

Amberの基本的な制御構造には以下の物があります。

ブロック

複数の文を記号{}で囲むとブロックとなります。ブロック内部の文は順番に実行され、最後の文の評価値がブロックの評価値となります。文が一つも無いブロック(空ブロック)の評価値はnilです。 ブロックの内部は新しいスコープとなります。

{
    文1
    文2
    ...
}

ブロック内の各文はインデントが揃っていなければなりません。以下の様にインデントがずれていると文法エラーとなります。インデント幅は任意です。

{
    puts("Hello")
  puts("World")
}

記号{}を用いない形式のブロックもありますがこれは後ほど紹介します。

シーケンス

seq {
    文1
    文2
    ...
}

と記述するとシーケンスとなります。ブロックと同様に文を順番に実行しますがスコープを作りません。例えば以下のようにシーケンスの外から内部の定義を参照する事が出来ます。

seq {
    x: 0
}
puts(x)         # => 0

シーケンスは主にマクロの実装などに利用します。普段のプログラミングで使用する場面は少ないでしょう。

また、演算子;を利用して

文1; 文2; 文3; ...

と記述してもシーケンスとなります。

文;

文; nil

の略となります。これはAmberのシェルを使う場合にいちいち評価値が表示されるのが煩わしい場合に利用出来る記法です。以下の様になります。

amber:1> x: [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
amber:2> y: [1,2,3,4,5];
=> nil

if文

if (条件文) ブロック

と記述するとif文となります。これは条件分岐を表して「条件文」がtrueを返す時には「ブロック」が実行されそれ以外ではnilとなります。 Amberでは真を表すのはシンボルtrueのみです。その他の条件文を実行してその他のオブジェクトが返った場合には全て偽として扱われます。1

if (条件文) ブロック1 else ブロック2

と記述するとif-else文となります。「条件文」がtrueを返した場合には「ブロック1」がそれ以外は「ブロック2」が実行されます。以前書いたフィボナッチ関数の例を条件分岐により定義してみると

fib(n): {
    if (n < 2) {
        n
    } else {
        fib(n-1) + fib(n-2)
    }
}

となります。

{}の省略

ブロックを表す{}は省略する事が出来ます。先ほどのfibの例は以下のようにも書けます。

fib(n):
    if (n < 2)
        n
    else
        fib(n-1) + fib(n-2)

ブロックの区切りはインデントで識別されるので文が2つ以上の場合には各文のインデントを揃えて下さい。

hello():
    print("Hello")
    print(" ")
    print("World")
    print("\n")

hello()     # => Hello World

{}が無くてもスコープは作られますので注意して下さい。

シェルでの複数行の文の入力

シェルではまだ入力途中だと判断出来る場合にのみ次の行に継続します。例えば以下の例を見て下さい。

amber:1> if true
amber:1~        puts("Hello")
Hello
=> nil

if trueの時点ではif文の途中だと判断されるので次の行に継続してプロンプトが~に変わっています。しかしputs("Hello")の時点でif文の入力が終わったと判断されます。従ってシェルで複数行のブロックを入力する場合には{}が必須です。

if-else文を書く場合には以下のように書きます。

amber:1> if true {
amber:1~        puts("true")
amber:1~ } else {
amber:1~        puts("false")
amber:1~ }
true
=> nil

以後のステートメントについても同様です。

while文

while (条件式) ブロック

と記述するとwhile文となります。「条件式」がtrueである間繰り返して「ブロック」が実行されます。while文の評価値は常にnilとなります。

for文

for (パターン in 式) ブロック

と記述するとfor文となります。この文は「式」の評価値の各要素を順番に「パターン」に代入しながら「ブロック」を実行します。

例えば、リストの要素を順番にイテレートするならば以下のようになります。

amber:1> for (v in [1,2,3,4,5]) {
amber:1~        puts(v)
amber:1~ }
1
2
3
4
5
=> nil

また、ペアのリストを順番にイテレートするならばパターンを用いて以下のように記述します。

amber:1> for ((x, y) in [(1, 2), (3, 4), (5, 6)]) {
amber:1~        puts(x + y)
amber:1~ }
3
7
11
=> nil

同様に配列やテーブルのイテレーションも出来ます。ユーザが定義したオブジェクトでもeachという関数を備えていればfor文を利用する事が出来ます。詳しくは標準ライブラリ:for文を参照して下さい。

Rangeオブジェクト

整数1..整数2

と記述すると「整数1」以上、「整数2」以下を表すRangeオブジェクトとなります。これを用いると整数を順番にイテレートするfor文を記述出来ます。

amber:1> for (i in 1..5) {
amber:1~        puts(i)
amber:1~ }
1
2
3
4
5
=> nil

break文とcontinue文

while文、for文のいずれにおいてもbreak文によってループを中断する事が出来ます。

amber:1> for (v in 1..5) {
amber:1~        if (v == 3) break
amber:1~        puts(v)
amber:1~ }
1
2
=> nil

またcontinue文を用いるとイテレーションを中断し、次のイテレーションにスキップする事が出来ます。

amber:1> for (v in 1..5) {
amber:1~        if (v == 3) continue
amber:1~        puts(v)
amber:1~ }
1
2
4
5
=> nil

例外機構

throw文

throw 式

と記述するとthrow文となります。任意の値を投げる事が出来、 後述するtry-catch文によってその値を受け取る事が出来ます。

try-catch文

try
    文
catch
    式

と記述すると「文」の実行中に投げられた値は「式」(ハンドラ)によってキャッチされます。 ハンドラの部分には任意の関数を記述する事が出来ます。

amber:1> try
amber:1~        throw "ERROR"
amber:1~ catch e -> {
amber:1~        puts(e)
amber:1~ }
ERROR
=> nil

投げられた値がハンドラの定義域外であった場合にはキャッチされず、更に外側のtry-catch文に向かって送出されます。

amber:1> try
amber:1~        try
amber:1~                throw 0
amber:1~        catch 1 -> {
amber:1~                puts("caught 1")
amber:1~        }
amber:1~ catch 0 -> {
amber:1~        puts("caught 0")
amber:1~ }
caught 0
=> nil

  • [1] ここの仕様がどうあるべきかについてはまだ悩んでいます。今後変更になる可能性があります。