1. 序章

このチュートリアルでは、Scalaの部分関数を見ていきます。 部分関数は、定義されたデータのサブセットに適用できる関数です。

たとえば、奇数でのみ機能する関数をIntドメインで定義できます。

2. 定義を理解する

Scaladoc の定義を見ると、強調すべき3つの主な特徴があります。

部分関数:

  • は単項演算です。つまり、1つのパラメーターのみを取ります。
  • 値のサブドメインに適用可能
  • 明示的にisDefinedAtメソッドを含めて、そのドメインを定義することができますおよびapplyメソッド

部分関数の例を見てみましょう。

val squareRoot: PartialFunction[Double, Double] = {
  def apply(x: Double) = Math.sqrt(x)
  def isDefinedAt(x: Double) = x >= 0
}

上記の関数を分析すると、次のことがわかります。

  • Doubleという単一のパラメーターを取ります
  • x> =0などのDoubleサブドメインに適用されます
  • isDefinedAtメソッドとapplyメソッドがあります

したがって、それは確かに部分関数です。

isDefinedAtおよびapplyは暗黙的に定義されることがよくありますケースステートメントを使用して関数を書き直すことができます。 部分関数を書くとき、それは本当に一般的な方法です:

val squareRootImplicit: PartialFunction[Double, Double] = {
  case x if x >= 0 => Math.sqrt(x)
}

負の数で部分関数を呼び出すと、scala.MatchErrorランタイムエラーが返されます。

3. またはElseおよびそしてとの連鎖

連鎖は、部分関数の非常に便利な機能です。

正の整数を負に、またはその逆に変換したいとします。 明らかに、整数-1を掛けるだけでうまくいきます。

ただし、連鎖機能を示すために、ソリューションには次のものが含まれます。

  • 数値が負またはゼロの場合は、その絶対値を返します
  • 数値が正の場合は、-1を掛けた数値を返します。

したがって、要件ごとに部分関数を定義できます

val negativeOrZeroToPositive: PartialFunction[Int, Int] = {
  case x if x <= 0 => Math.abs(x)
}

val positiveToNegative: PartialFunction[Int, Int] = {
  case x if x > 0 => -1 * x
}

次に、連鎖演算子またはElse を活用して、それらを一緒に使用できます。

val swapSign: PartialFunction[Int, Int] = {
  positiveToNegative orElse negativeOrZeroToPositive
}

さらに、 PartialFunctionトレイトには、andThen も含まれています。その結果、連鎖は非常に簡単に実現できます。

andThen がどのように機能するかを示すために、正のInt値のみを出力する関数から始めましょう。

val printIfPositive: PartialFunction[Int, Unit] = {
  case x if x > 0 => println(s"$x is positive!")
}

これで、swapSign関数を使用してこれを連鎖させることができます。

(swapSign andThen printIfPositive)(-1)

最終的な結果は、非常に読みやすいチェーンコードです。

4. コレクションの操作

コレクションを操作する場合、部分関数に適した方法がいくつかあります。

それほど明白ではない方法で動作する、主要なもののいくつかを見てみましょう。

4.1. 収集、マッピング、およびフィルタリング

collect は、コレクションが与えられると、そのドメイン内の要素に部分関数を適用することによって新しいコレクションを返すメソッドです。

collectメソッドで使用される部分関数を定義しましょう。

val parseRange: PartialFunction[Int, Int] = {
  case x: Int if x > 10 => x + 1
}
List(15, 3, "aString") collect { parseRange }

リスト10より大きい唯一のIntであるため、部分関数は15にのみ適用されます。

したがって、上記は List(16)。を返します。

コンパイルエラーを発生させることなく、マップでparseRangeを使用することもできます

List(15, 3, "aString") map { parseRange }

パターンマッチはStringの処理方法を知らないため、このコードは実行時にscala.MatchErrorをスローします

最後に説明するメソッドはfilterです。これは、 collectに似ていますが、ただし filter は、指定された条件を満たす要素のコレクションを返します。

同じ部分関数を使用して返されるさまざまな結果を見ると、収集メソッドとフィルターメソッドの動作が異なることがわかります

List(1, 2) collect { case i: Int => i > 10 }
List(1, 2) filter { case i: Int => i > 10 }

この場合、 collectList(false、false)を返し、filterは空のリストを返します。 また、部分関数を無名関数として定義した方法にも注目してください。

5. 結論

この記事では、部分関数について説明しました。 部分関数を連鎖させ、それらを収集メソッドで使用するのは非常に簡単であることを示しました。

非常に構成可能なコードを目指すときはいつでも、部分関数は間違いなく覚えておくべきものです。 特にコレクションで使用する場合、 collect map、 filter などのメソッドを使用すると、データの操作と変換が簡単になります。

これらの特性の組み合わせにより、部分関数は強力なツールになります。

いつものように、コードはGitHubから入手できます。