Skip to content

Instantly share code, notes, and snippets.

@gakuzzzz
Last active August 2, 2023 01:59
Show Gist options
  • Star 234 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save gakuzzzz/10104162 to your computer and use it in GitHub Desktop.
Save gakuzzzz/10104162 to your computer and use it in GitHub Desktop.
Scala の省略ルール早覚え

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_)
}

なぜその様な特殊なルールになっているか、というと、理想としては

  • 副作用を伴わないメソッド(getterみたいなやつ)は括弧を付けない
  • 副作用を伴うメソッドは括弧をつける

という方向にしたいからです。

単純に定義時に () つけたメソッドは省略不可、定義時に () つけなかったメソッドは () の記述不可 としてしまうと、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" が得られる

Function0Function22 の型は => を使って記述する事ができます。

val f: (Int) => String = new Function1[Int, String] {
  def apply(arg: Int): String = arg.toString
}
f.apply(10) // "10" が得られる

Function0Function22 のインスタンスは、 => を使って記述することができます。

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 式と同じ書き方で、Function0Function22 もしくは PartialFunction を定義することができます。

val pf: PartialFunction[Int, String] = {
  case 1 => "AAA"
  case 2 => "BBB"
}
pf(1)  // "AAA"
pf(3)  // MatchError が投げられる

PartialFunctionFunction1 のサブトレイトで、とりうる引数の値のうち、一部の値のみ結果を返す関数を表します。

Seq(1, 2, 3) map {
  case 1 => "AAA"
  case 2 => "BBB"
  case _ => "ZZZ"
}

Function0Function22 を要求するメソッドの引数でも、直接パターンマッチが使えるので非常に便利です。

パターンマッチ無名関数が 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 を要求していない時は変換せずにコンパイルエラーにしてくれるように そういったルールになっています。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment