Scala の省略ルール早覚え
このルールさえ押さえておけば、読んでいるコードが省略記法を使っていてもほぼ読めるようになります。
def concatAsString(a: Int, b: Int): String = {
val a_ = a.toString();
val b_ = b.toString();
return a_.+(b_);
}
セミコロンは省略できます。
def concatAsString(a: Int, b: Int): String = {
val a_ = a.toString()
val b_ = b.toString()
return a_.+(b_)
}
引数を持たない且つ、定義時に () ありで定義したメソッドは、呼び出し時に () を省略できます。
def concatAsString(a: Int, b: Int): String = {
val a_ = a.toString
val b_ = b.toString
return a_.+(b_)
}
なぜその様な特殊なルールになっているか、というと、理想としては
という方向にしたいからです。
単純に定義時に () つけたメソッドは省略不可、定義時に () つけなかったメソッドは () の記述不可 としてしまうと、Java のライブラリを呼び出すときに整合性がつかなくなるので、 Javaとの互換を意識して一見すると妙なルールになっています。
そしてブロックの最後の式の結果がブロック全体の結果となります。したがってブロック内の途中でメソッドの評価結果を決定したい場合を除いて、return は明示的に書く必要がありません。
def concatAsString(a: Int, b: Int): String = {
val a_ = a.toString
val b_ = b.toString
a_.+(b_)
}
単一の引数をとるメソッドは、ドットと引数グループの括弧を省略できます。
def concatAsString(a: Int, b: Int): String = {
val a_ = a.toString
val b_ = b.toString
a_ + b_
}
ドットを省略した場合、ドットを使った呼び出しよりも結合が弱くなります。
def concatAsString(a: Int, b: Int): String = {
a.toString + b.toString
}
メソッド定義のブロックは複数の式を一つの式にまとめるブロック式と呼ばれるものなので、1つの式しか持たない場合ブロック式にする必要がなくなります。
def concatAsString(a: Int, b: Int): String = a.toString + b.toString
再帰したメソッドなどを除いて、戻り値の型が自明の場合は、戻り値の型アノテーションを省略できます。
def concatAsString(a: Int, b: Int) = a.toString + b.toString
できますが、public なメソッドに関しては、省略をおすすめしません。
val f: Function1[Int, String] = new Function1[Int, String] {
def apply(arg: Int): String = arg.toString
}
f.apply(10) // "10" が得られる
Function0
~ Function22
の型は =>
を使って記述する事ができます。
val f: (Int) => String = new Function1[Int, String] {
def apply(arg: Int): String = arg.toString
}
f.apply(10) // "10" が得られる
Function0
~ Function22
のインスタンスは、 =>
を使って記述することができます。
val f: (Int) => String = (arg: Int) => arg.toString
f.apply(10) // "10" が得られる
引数の型が自明の時は、型アノテーションを省略する事ができます。
val f: (Int) => String = (arg) => arg.toString
f.apply(10) // "10" が得られる
引数が一つの場合(つまりFunction1
の時)、引数グループの括弧を省略できます。
val f: Int => String = arg => arg.toString
f.apply(10) // "10" が得られる
全ての引数が、1回のみ使われる場合は、引数の宣言を省略し、_ で表現することができます。
val f: Int => String = _.toString
f.apply(10) // "10" が得られる
apply
という名前のメソッドは省略することができます。
val f: Int => String = _.toString
f(10) // "10" が得られる
match 式と同じ書き方で、Function0
~ Function22
もしくは PartialFunction
を定義することができます。
val pf: PartialFunction[Int, String] = {
case 1 => "AAA"
case 2 => "BBB"
}
pf(1) // "AAA"
pf(3) // MatchError が投げられる
PartialFunction
は Function1
のサブトレイトで、とりうる引数の値のうち、一部の値のみ結果を返す関数を表します。
Seq(1, 2, 3) map {
case 1 => "AAA"
case 2 => "BBB"
case _ => "ZZZ"
}
Function0
~ Function22
を要求するメソッドの引数でも、直接パターンマッチが使えるので非常に便利です。
パターンマッチ無名関数が Function1
になるか、PartialFunction
になるかは、要求されている場所によります。
Function1
が要求されている場所では PartialFunction
ではなく Function1
になるため、パターンの網羅性検査が行われます。
// Function1 を要求している場合、網羅性検査で漏れが見つかると warning が出ます。
scala> val f: Function1[Option[Int], Int] = { case Some(x) => x }
<console>:7: warning: match may not be exhaustive.
It would fail on the following input: None
val f: Function1[Option[Int], Int] = { case Some(x) => x }
^
f: Option[Int] => Int = <function1>
// PartialFunction を要求している場合は網羅性検査が行われません。
scala> val p: PartialFunction[Option[Int], Int] = { case Some(x) => x }
p: PartialFunction[Option[Int],Int] = <function1>
メソッドから FunctionN
のインスタンスを生成できます。
def add(a: Int, b: Int): Int = a + b
というメソッドがあった時に、後ろに _
を付けると Function2
のインスタンスがとれます
val f = add _ // f: (Int, Int) => Int = <function2> と評価される
明確に FunctionN
が要求されている事が判明している場合は、 _
を省略できます。
val f: (Int, Int) => Int = add
大抵の場合は FunctionN
を引数にとるメソッドに渡す感じになるので _
はあまり使わないです。
じゃあなんでそんなルールになっているかというと、
println(add) // 引数を与え忘れ
とかした時に (Int, Int) => Int = <function2>
とか出力されても何も嬉しくないので、
明示的に FunctionN
を要求していない時は変換せずにコンパイルエラーにしてくれるように
そういったルールになっています。