制御構文

Takami Torao Java 9 Scala 2.12 Julia 0.6 #java #scala #julialang
  • このエントリーをはてなブックマークに追加

複合式

複合式 (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 の条件評価 exprboolean 型のみを受け付ける。

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 上では \inTab で入力可能)。

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 を生成する。Rangeby でステップ数を指定することができる。

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 は breakcontinue 構文が存在しない。例外を使って実装された break は用意されているが関数型言語でのループの制御は一般的ではない。

import scala.util.control.Breaks
val b = new Breaks()
b.breakable {
  for(i <- 0 until 10){
    if(i == 4) b.break
    print(i)
  }
}

関数型言語でループ制御やループカウンタの変更といったより複雑なフローを実装するには一般的に forwhile ではなく末尾再帰 (tail recursion) を使用する。

while 構文

while はある条件が満たされるまで処理を繰り返す制御構文。有限回数をループ開始前に決定する方針の for 構文に対して while 構文が定義するのは停止条件のみである。このため最近では設計が弱いといった風潮があり while を使わないで済む設計を考えることが推奨されている。もちろん 100% 使用しないのは無理があるが。

Java / Scala

while(expr){
  proc
}

exprfalse となるまで proc の実行を繰り返す。

Julia

while x == y
end

do-while 構文

Java / Scala

do {
  proc
} while(expr);

exprfalse となるまで proc の実行を繰り返す。

Julia

Julia に do-while 構文はないが、while-break を用いて マクロを使用して定義することが出来る

例外処理

try-catch 構文

Java

try{
  i = Integer.parseInt(s);
} catch(NumberFormatException ex){
  ex.printStackTrace();
}

exprfalse となるまで proc の実行を繰り返す。

Java / Scala

do {
  proc
} while(expr);

exprfalse となるまで proc の実行を繰り返す。

Julia

Julia に do-while 構文はないが、while-break を用いて マクロを使用して定義することが出来る