水底

ScalaとかC#とかk8sとか

Scala糖衣構文の罠

ScalaMatsuriで糖衣構文について話そうとしてその導入部分に使う予定だったものを昔に書きなぐっていたので(CFP落ち). あくまで特異な一例です

糖衣構文の罠

1つScala界の一部で有名な(?)期待を裏切るサンプルを紹介する

def f: Int = {
  1 + 2
}

def g: Int = {
  1 +
  2
}

def h: Int = {
  1
  + 2
}

一見関数 f, g, h は全て 3 を返すように見えるが, 実際にはそうはならない.

println(f) // 3
println(g) // 3
println(h) // 2

どうしてこのようなことになるのか

実は f, g+h+ は全く別物である. 前者は多くの人が期待するであろう (Int, Int) => Int となる和の演算を表す二項演算子(実際には2値を引数に取る関数の糖衣構文 1.+(2)), 一方後者は前置演算である 符号の + (実際には unary_ による糖衣構文 1.unary_+)である. つまり, + 2 の部分は単に正の値を表しているだけであり, 上段の 1 は利用されていない. 例えばIntellijなどを使うとご丁寧に 1 の部分が Useless expression だと教えてくれるだろう. もう少し分かりやすいサンプルに変えてみよう.

def f0: Int = {
  1 - 2
}

def g0: Int = {
  1 -
  2
}

def h0: Int = {
  1
  - 2
}

これらを実行すると,

println(f0) // -1
println(g0) // -1
println(h0) // -2

となる. この見た目に反するような挙動を示す原因はあくまで unary_ で定義される前置の関数2値を引数に取る関数 が偶然同じ見た目であることであり, 例えば積の演算を期待した * で同様に記述しようとしても * が前置の関数になり得ないためコンパイルエラーとなる.

同じことを別の視点からも見ておこう. Scalaにおける {} (ブロック式)は内部に任意個数の式を持ち, 最後の式の評価結果を全体の評価結果とする単一式 である. このことを踏まえると, f, g はどちらも 1 + 2 という1つの式を持ってその評価結果を返すが, h1+ 2 という2つの式を持っており, 最後の + 2 の評価結果が返っているのである.

結論

改行と結合規則を察して

コンパイラのお気持ちを汲み取れ

(まぁ普通こんな変な改行しませんよね)

(Scalaにはすごーい!糖衣構文フレンズが沢山いるので仲良くなろう)