このページの内容

制御フロー

制御フローは、どのコードを何回実行するかを決めるために使います。分岐の文や式は、あるコードを実行するかどうかを決め、ループの文や式は、同じものを複数回実行します。

真偽の判定 #

すべての制御フローは、何かをするかどうかの判断に基づいています。この判断は、ある式の値によって決まります。考えられるすべてのオブジェクトを二つに分け、一方を「真」、残りを「偽」と見なします。式の結果が真の側の値なら、ある動作を行います。そうでなければ、別の動作を行います。

当然、真偽値trueは「真」、falseは「偽」に入ります。しかし、ほかの型の値はどうでしょうか? この選択に絶対的な決まりはなく、言語ごとに規則が異なります。Wrenの規則はRubyに従っています。

  • 真偽値falseは偽です。
  • null値nullは偽です。
  • それ以外はすべて真です。

つまり、0、空の文字列、空のコレクションも、すべて「真」の値と見なします。

if文 #

最も単純な分岐文であるifは、条件に応じてコードを読み飛ばします。次のように書きます。


if (ready) System.print("go!")

ifの後にある、丸括弧で囲んだ式を評価します。真なら、条件の後の文を評価します。そうでなければ、その文を読み飛ばします。単独の文の代わりに、ブロックも書けます。


if (ready) {
  System.print("getSet")
  System.print("go!")
}

elseの分岐を設けることもできます。条件が偽の場合は、こちらを実行します。


if (ready) System.print("go!") else System.print("not ready!")

もちろん、こちらもブロックにできます。


if (ready) {
  System.print("go!")
} else {
  System.print("not ready!")
}

論理演算子 #

Wrenのほかの多くの演算子は、メソッド呼び出しの特別な構文にすぎませんが、&&と||は特別です。右のオペランドを条件に応じてしか評価しない、短絡評価を行うためです。

&&(「論理AND」)の式は、左の引数を評価します。偽なら、その値を返します。そうでなければ、右の引数を評価して返します。


System.print(false && 1)  //> false
System.print(1 && 2)      //> 2

||(「論理OR」)の式は逆です。左の引数が真なら、それを返します。そうでなければ、右の引数を評価して返します。


System.print(false || 1)  //> 1
System.print(1 || 2)      //> 1

条件演算子?: #

Wrenには、Cや似た言語でおなじみの、小さな「式の形をしたif文」があります。三つの引数を取るので、「三項」演算子とも呼ばれます。


System.print(1 != 2 ? "math is sane" : "math is not sane!")

条件式、?、then側の式、:、else側の式の順に書きます。ifと同じように条件を評価し、真ならthen側の式を評価して返します。そうでなければ、else側の式を評価して返します。

while文 #

コードの一部分を繰り返し実行しなければ、役に立つプログラムを書くのは難しいでしょう。そのために、ループ文を使います。Wrenには二つあり、ほかの命令型言語を使ったことがあれば、なじみのあるものです。

最も単純なwhile文は、条件が成り立ち続ける限り、コードを実行します。例えば、次のように書きます。


// Hailstone sequence.
var n = 27
while (n != 1) {
  if (n % 2 == 0) {
    n = n / 2
  } else {
    n = 3 * n + 1
  }
}

式n != 1を評価し、真なら、その後の本体を実行します。その後、先頭に戻って条件をもう一度評価します。条件が真と見なされる値になる限り、これを繰り返します。

whileループの条件には任意の式を使えますが、丸括弧で囲む必要があります。ループの本体は、通常は波括弧で囲んだブロックですが、単独の文でもかまいません。


var n = 27
while (n != 1) if (n % 2 == 0) n = n / 2 else n = 3 * n + 1

for文 #

while文は、無期限に、または複雑な条件に従って繰り返したい場合に便利です。しかし、多くの場合はリスト、一連の数値、その他の「シーケンス」オブジェクトを順に処理します。forは、そのための文です。次のように書きます。


for (beatle in ["george", "john", "paul", "ringo"]) {
  System.print(beatle)
}

forループは、三つの部分からなります。

  1. 値を結び付ける変数名。例ではbeatleです。Wrenは、この名前の新しい変数を作ります。そのスコープは、ループの本体です。

  2. シーケンス式。何を反復処理するかを決めます。ループ本体の実行前に一度だけ評価します。この例ではリストリテラルですが、任意の式を使えます。

  3. 本体。波括弧で囲んだブロックか、単独の文です。ループの各反復で一度実行します。

break文 #

ループの本体の途中で、抜け出して終了したくなることがあります。そのためにbreak文を使えます。breakキーワードを単独で書くだけです。直ちに、最も内側の、現在の文を囲むwhileまたはforループを抜けます。


for (i in [1, 2, 3, 4]) {
  System.print(i)           //> 1
  if (i == 3) break         //> 2
}                           //> 3

continue文 #

ループ本体の実行中に、現在の反復の残りを読み飛ばして次の反復へ進みたくなることがあります。そのためにcontinue文を使えます。continueキーワードを単独で書くだけです。直ちに次のループ反復の先頭へ移り、ループの条件を確認します。


for (i in [1, 2, 3, 4]) {
  System.print(i)           //> 1
  if (i == 2) continue      //> 3
}                           //> 4

数値の範囲 #

リストはforループのよくある用途ですが、一連の数値を順に処理したり、一定の回数だけ繰り返したりしたい場合もあります。その場合は、次のように範囲を作れます。


for (i in 1..100) {
  System.print(i)
}

これは100そのものも含め、1から100までの数値について繰り返します。最後の値を含めたくなければ、点を二つではなく三つにします。


for (i in 1...100) {
  System.print(i)
}

forループ専用の「範囲」構文に見えますが、実際には二つの演算子です。..と...は、中置の「範囲」演算子です。ほかの演算子と同様、通常のメソッド呼び出しの特別な構文です。数値型がこれらを実装し、一連の数値を反復処理する方法を知っている範囲オブジェクトを返します。

イテレータープロトコル #

リストと範囲で、最も一般的な二種類のループを扱えますが、自分のシーケンスも定義できる必要があります。そのために、forの意味は「イテレータープロトコル」で定義されています。ループ自体はリストや範囲を知りません。シーケンス式を評価して得たオブジェクトに対し、特定の二つのメソッドを呼び出す方法だけを知っています。

次のようなループを書くと、


for (i in 1..100) {
  System.print(i)
}

Wrenには、おおよそ次のように見えます。


var iter_ = null
var seq_ = 1..100
while (iter_ = seq_.iterate(iter_)) {
  var i = seq_.iteratorValue(iter_)
  System.print(i)
}

まず、Wrenはシーケンス式を評価し、隠れた変数に保存します(例ではseq_と書いていますが、実際には利用できる名前はありません)。また、隠れた「イテレーター」変数を作り、nullで初期化します。

各反復で、現在のイテレーターの値を渡してシーケンスのiterate()を呼び出します(最初の反復ではnullを渡します)。シーケンスの役目は、そのイテレーターを受け取って次の要素へ進めることです(イテレーターがnullの場合は、最初の要素へ進めます)。その後、新しいイテレーターか、もう要素がないことを示すfalseを返します。

falseが返ると、Wrenはループを抜けて終了します。それ以外が返ると、新しい有効な要素へ進んだという意味です。その要素を得るため、Wrenは続いてシーケンスのiteratorValue()を呼び出し、iterate()から得たばかりのイテレーターの値を渡します。シーケンスはそれを使って、該当する要素を調べて返します。

組み込みのList型とRange型は、それぞれのシーケンスを順に処理するため、iterate()とiteratorValue()を実装しています。自分のクラスでも同じメソッドを実装すれば、独自の型を反復処理できるようになります。



変数 →← メソッド呼び出し