if、while、for

C言語などと同様の制御構造がscalaにも存在する。

if式

if式の構文は以下のとおり。C言語などのものとほぼ一緒。

if(条件式) {処理1}
else {処理2}

条件式が評価され、結果が真ならば処理1が実行される。条件式が偽ならば処理2が実行される。処理が1行だけならば中括弧は省略可能。


if式はelseと用いることで、C言語などで言うところの三項演算子(?:)と同じ動作になる。つまり、if式自身が値を返す。

scala> val x = 1  
x: Int = 1

scala> val y = if(x > 0) 10 else -10 // yには10が入る(xが0より大きいため)
y: Int = 10

ただし、elseと一緒に用いない場合はif式は値を返さない。

scala> val x = 1  
x: Int = 1

scala> val y = if(x > 0) 10 // xが0より大きいにもかかわらず、10は代入されない
y: Unit = ()

この動作はセマンティクスとしても正しい。elseが存在しない場合、ifの条件が成立しなかった時(上記例ではxが0以下だった場合)、変数yの値が確定しなくなる。


ちなみに余談だがHDLというハードを設計するための言語でも、ifを利用する場合はelseをきちんと定義することを強く推奨されている。ハードのワイヤ(電気信号を送る電線などのこと)は「0」もしくは「1」の2つの状態を常に送る必要があり、ifとelseがセットになっていないと、ifの条件式が真にならないときのワイヤの状態が予期しないものとなってしまう(正確にはラッチという設計者が予期しないモジュールが生成されてしまう)。

whileループ

whileループはif〜else〜と違い、値は返さない。

whileループの種類には、whileとdo…whileがある。

while(条件式) {処理}   // whileループ
do{処理} while(条件式) // do…whileループ

whileループは最初に条件式を評価し、真ならば処理を実行をする。条件式が偽になるまでこれを繰り返す。

do…whileループはまず処理を実行し、その後に条件式を評価する。条件式が偽になるまでこれを繰り返す。

以下はwhileループの例。

scala> var a = 0
a: Int = 0

scala> var b = 0
b: Int = 0

scala> while(a <= 10) {
     | b += a
     | a += 1
     | }

scala> b
res5: Int = 55

whileループとdo…whileループの利用方法での大きな違いは、少なくとも1回は処理を実行させたい場合にはdo…whileを使う、という点。

scala> var a = 10  
a: Int = 10

scala> while(a < 5) {
     | println("Hello, world") // 先に条件式が評価されるので実行されない
     | }

scala> do {
     | println("Hello, world") // 後で条件式が評価されるので実行される
     | } while(a < 5) 
Hello, world

whileループはできる限り利用しないほうがよい。前述の例のように、whileループはvar変数を前提とした処理が多い。これは手続き型言語系によく見られる処理方法。P

whileループを利用しようとするときは、コードの可読性があがるなどの正当な理由があるときのみとすること。scalaでは、大体の処理はリストなどのメソッドや再帰関数で代替可能。

for式

for式はscalaのミソともいえる制御構造です。for式の説明だけ、いずれ独立させるかも…。

for式の基本

forはさまざまな書き方があるので、最初は混乱するかもしれない。基本としては以下の構文(ただし、必ずしも正確な表現ではなく、あくまでイメージ)。

for(変数 <- 配列) {処理}

まず1つ目は以下のような書き方。

scala> for(val i:Int <- 1 to 5)  // iに1から5まで代入して、
     | println(i)  // printlnで表示する
1
2
3
4
5

変数iのvalは省略可能。ただし、varを指定することはできない。また、Int型を省略(型推論)することも可能。
1 to 5 は1から5までの配列を作る。
つまりこのfor式は、1から5までの配列(正確にはRange)を作成し、その配列の各要素を変数iに格納、iをprintlnで表示する、という動作をしている。

byを用いることで、1から5までの配列の間隔を指定できます。表現が難しいので以下の例を参照してください。

scala> for(i <- 1 to 5 by 2) // 「by 2」を指定することで、1つ飛ばしの配列が作成される。
     | println(i)
1
3
5

また、以下のように後述するListなどを用いることも可能。

scala> val lst = List[Int](1,2,3,4,5) // Int型のリスト。リストなどに関しては後ほど。
lst: List[Int] = List(1, 2, 3, 4, 5)

scala> for(elem <- lst)
     | println(elem)
1
2
3
4
5

ただし、List方にはbyは用いることはできません。

scala> for(i <- lst by 2)
     | println(i)
<console>:6: error: value by is not a member of List[Int] // エラーとなる
       for(i <- lst by 2)
for式による値生成

for式は、yieldを用いることで値(コレクション)を作成することができる。

scala> val seq = for(i <- 1 to 5) yield i + 2
seq: RandomAccessSeq.Projection[Int] = RangeM(3, 4, 5, 6, 7)

上記の例では、seqに配列(のようなもの) (3,4,5,6,7) が、for式により作成され代入される。

yieldをつけない場合は以下のようになってしまう。つまりseqはUnit型、値が生成されていない。

scala> val seq = for(i <- 1 to 5) i + 2
seq: Unit = ()

また、for式の本体処理を中括弧でくくる場合、yieldは中括弧の前に記述する。

scala> val seq = for(i <- 1 to 5) yield {i + 2} // 中括弧の前にyieldを記述
seq: RandomAccessSeq.Projection[Int] = RangeM(3, 4, 5, 6, 7)
フィルタリング

単純に繰り返し処理をするだけでなく、繰り返し処理を実行する要素を絞り込むための構文が存在する。

for(変数 <- 配列 if 条件式) {処理}

以下は例。1から5までの配列要素の中で、2の剰余が0、つまり偶数の要素だけを抽出(フィルタリング)し、表示します。

scala> for(i <- 1 to 5 if i % 2 == 0) // %は剰余(割り算の余り)を求める演算子
     | println(i)
2
4

続く...