制御構文
複合式
複合式 (compound expression) またはブロック (block) は複数の構文を一つにまとめる構文。目的は一時的な変数や関数の定義スコープを最小限にし影響を限定すること。一般的な関数型言語では式として最後の評価値を結果とすることができる。
ラムダや do{ ... } while(false);
で代用することもできる。
Java
double s = 0.0;
{
double h = 4.31;
double r = 2.25;
s = Math.PI * r * r * h;
}
// s ==> 68.54758820592079
中括弧 {}
で囲って変数スコープを限定することができる。ただし式ではないので結果を得ることはできない。
Scala
val s = {
val h = 4.31
val r = 2.25
math.Pi * r * r * h
} // => 68.54758820592079
Java と同様に中括弧 {}
を使用できる。最後の評価結果は値として使用することができる。
object X
{
// X のフィールドと見なされる
}
object Y
locally {
// Y のフィールドとは見なされない
}
Scala は構文の柔軟さから複合式が意図せずラムダなどに解釈されコンパイルエラーを起こすことがある。そのような場合は Predef
に定義されている locally
を使用する。
Julia
s = begin
h = 4.31
r = 2.25
pi * r * r * h
end # => 68.54758820592079
Julia では begin
, end
または ()
にまとめた構文を ;
で区切る。
s = (h = 4.31; r = 2.25; pi * r * r * h)
ただしこれらの複合式では変数のスコープを限定することができない。変数スコープを限定することが目的の場合は let
を使用する。
s = let h = 4.31, r = 2.25
pi * r * r * h
end
条件分岐
if-elseif-else 構文
Java
if(expr1){
proc1
} else if(expr2){
proc2
} else if(expr3){
proc4
} else {
proc5
}
Java の条件評価 expr
は boolean
型のみを受け付ける。
if 文は式ではないため評価結果は返さない。if
構文のショートカットである 3 項演算子 expr? proc1: proc2
は式として使用することができる。
int y = x < 0? -x: x;
proc
が単一文の場合は中括弧を省略することが可能だが、記述ミスによるバグを嫌って中括弧を必須とするコーディング規約も多い (過去に見かけた Tomcat のソースに以下のようなバグがあった)。
if(expr)
log(...);
proc;
Scala
if(expr1){
proc1
} else if(expr2){
proc2
} else if(expr3){
proc4
} else {
proc5
}
Scala の if 構文は Java と同じだが、式として扱われるため評価結果を取得することができる。C や Java のような 3 項演算子は if 構文で代用できるため存在しない。
val y = if(x < 0) -x else x
else
を省略し条件に一致しなかった場合は Unit
型の ()
が評価結果となる。
Julia
if expr1
proc1
elseif expr2
proc2
elseif expr3
proc3
else
proc4
end
条件評価は Bool
の結果となる式のみが可能。Scala と同様に if 構文自体が式であるため分岐結果を参照することができる。
julia> x = if -100 < 0 "negative" else "positive" end
"negative"
else
を省略し条件に一致しなかった場合は Void
値をとる。また Julia では C や Java のような 3 項演算子も使用できる。
julia> -100 < 0? "negative": "positive"
"negative"
繰り返し
for 構文
for
は特定のコレクション (配列やリスト、マップ、範囲などの列挙型等) に属する要素を順に操作するための構文。C/C++ などの低中級言語ではループカウンタを扱うことを目的としていたが、抽象度が高いプログラミング言語ではループを開始する時点でループ回数が決定されていることがより強固な設計との立場で、最近はコレクションや列挙型 (iterator/iterable) に対する数え上げでの用途が主流となってきた。
Java
int stringCount(Collection<String> c, String s){
int count = 0;
for(String ss: c){
if(ss.equals(s)){
count ++;
}
}
return count;
}
Java の for
構文はコレクション/列挙型の要素に対するループとカウンタを使用するループの 2 つの構文がある。
for(init: collection){ ... }
は Java 5 で導入された列挙型のループである。コレクションに対する数え上げであればこの構文を使用すれば良いだろう。
int charCount(String s, char ch){
int count = 0;
for(int i=0; i<s.length(); i++){
if(s.charAt(i) == ch){
count ++;
}
}
return count;
}
ループカウンタを使う for(init; eval; increment){ ... }
はカウンタ変数の操作だけであるため不要なオブジェクトやメソッドコールのオーバーヘッドが発生せずパフォーマンスが良い。ただしループ内でカウンタ値の変更を伴うような複雑な処理でなければ冗長であるし、そのような複雑なループ制御はバグの原因となる。
C/C++ と同様に for(;;)
は無限ループとして使用することができる。
jshell> for(int i=0; i<10; i++){
...> if(i == 4) break;
...> if(i == 2) continue;
...> System.out.println(i);
...> }
0
1
3
break
でループを中断することができる。また continue
で後の処理をスキップし次のループを継続する。
Julia
julia> x = [1,2,3,4]
julia> for i = 1:length(x)
print(x[i])
end
1234
Julia は範囲 UnitRange
を使用してループカウンタ型の繰り返し処理を行うことができる。ループカウンタ i
は for ブロック内のスコープを持つ。カウンタの値を変更することはできるが、ループごとにカウンタの初期化が行われるため、次回のループに変更の影響が残ることはない。
julia> for i = 1:5
if i == 4 break end
if i == 2 continue end
println(i)
end
1
3
for ブロックは break
で抜けることができる。また continue
で続きをスキップして次の繰り返しを行うことができる。
for i in [2, 3, 5, 7]
println(i)
end
コレクションの列挙は for-in
を使用する。in
の代わりに数学記号 ∈
を使用することができる (RELP 上では \in
Tab で入力可能)。
Scala
for(i <- 0 until list.length){
val s = list(i)
...
}
Scala の for 構文はコレクション API の syntax sugar で以下のように変換される。
0.until(list.length).foreach { i =>
...
}
Scala の for 構文はコレクションの数え上げや範囲の走査であってC/C++ やJava とはセマンティクスが異なる。繰り返し処理中にループカウンタ値を変更することはできない。
数値を左辺値 (レシーバー) とした until()
や to()
は列挙可能な Range
を生成する。Range
は by
でステップ数を指定することができる。
for(i <- 0 until 10 by 2){
...
}
yield 付き for 構文はコレクション API の foreach()
ではなく map()
、つまりループによってコレクションを生成する。これは Haskell や Python でのリスト内包表記 (list comprehension) に相当する。
scala> val a = Array(0, 1, 2, 3)
a: Array[Int] = Array(0, 1, 2, 3)
scala> for(i <- 0 until a.length) yield a(i) * a(i)
res70: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 1, 4, 9)
Scala では多重 for ループを一つにまとめることができる。
scala> for(x <- 0 until 3; y <- 5 until 8) yield x + y
res72: scala.collection.immutable.IndexedSeq[Int] = Vector(5, 6, 7, 6, 7, 8, 7, 8, 9)
Scala は break
や continue
構文が存在しない。例外を使って実装された break
は用意されているが関数型言語でのループの制御は一般的ではない。
import scala.util.control.Breaks
val b = new Breaks()
b.breakable {
for(i <- 0 until 10){
if(i == 4) b.break
print(i)
}
}
関数型言語でループ制御やループカウンタの変更といったより複雑なフローを実装するには一般的に for
や while
ではなく末尾再帰 (tail recursion) を使用する。
while 構文
while
はある条件が満たされるまで処理を繰り返す制御構文。有限回数をループ開始前に決定する方針の for
構文に対して while
構文が定義するのは停止条件のみである。このため最近では設計が弱いといった風潮があり while を使わないで済む設計を考えることが推奨されている。もちろん 100% 使用しないのは無理があるが。
Java / Scala
while(expr){
proc
}
expr
が false
となるまで proc
の実行を繰り返す。
Julia
while x == y
end
do-while 構文
Java / Scala
do {
proc
} while(expr);
expr
が false
となるまで proc
の実行を繰り返す。
Julia
Julia に do-while
構文はないが、while-break
を用いてマクロを使用して定義することが出来る。
例外処理
try-catch 構文
Java
try{
i = Integer.parseInt(s);
} catch(NumberFormatException ex){
ex.printStackTrace();
}
expr
が false
となるまで proc
の実行を繰り返す。
Java / Scala
do {
proc
} while(expr);
expr
が false
となるまで proc
の実行を繰り返す。
Julia
Julia に do-while
構文はないが、while-break
を用いてマクロを使用して定義することが出来る。