[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
Scala 2.13からscala.util.chaining
パッケージが追加されました。
これは、Scalaで手軽にメソッドチェーンを構築するためのパッケージです。
2.12以前のScalaでも限定的な状況下ではメソッドチェーンをすることができましたが、 以前よりさらに手軽にメソッドチェーンをすることができるようになりました。
この記事ではscala.util.chaining
を利用して、メソッドチェーンを作って処理を行う方法を解説します。
メソッドチェーンとは「処理の連鎖」
メソッドチェーンとは、一連の処理を連鎖的に繋げて書くことです。
メソッドがまるで鎖のようにつながっているさまを指して「メソッドチェーン」と呼んでいます。
実装上は、メソッドの処理の結果としてオブジェクト自身を返すことによって実現されることが多いです。
例えばScalaにおいても、Option
のmap
メソッドはOption
を返すため、
Option
のもつメソッドを何個も繋げてメソッドチェーンをすることができます。
Option(1) .map(_ + 2) .map(_ * 3) .foreach(println)
ScalaではOption
などの値を包みこむような構造の型を使っている場合にメソッドチェーンができることが多かったですが、
scala.util.chaining
によって、そうでない型のオブジェクトに対してもメソッドチェーンができるようになりました。
Option
などの、値を包みこむような構造のクラスであればmap
やforeach
メソッドが備わっていることが多いので、
すでにオブジェクトがそれらのクラスである場合はそれを使えばOKです。
他の言語におけるメソッドチェーン
他の言語においてメソッドチェーンを活用している例としては、特にjQueryが有名です。
メソッドチェーンの使い方
scala.util.chaining._
をインポートするだけ
Scalaでメソッドチェーンをするには、scala.util.chaining
パッケージの中身をインポートします。
import util.chaining.*
これにより、スコープ内のあらゆるオブジェクトがpipe
やtap
メソッドを持つようになります。
その理由は暗黙の型変換によるのですが、後ほど詳述します。
tap
メソッドは副作用を発生させるために使う
副作用が発生するような処理の場合はtap
メソッドを使います。
1.tap(n => println(s"tapped $n")) .pipe(_ * 3) .tap(n => println(s"tapped $n"))
結果は以下のようになります。
tapped 1 tapped 3
pipe
メソッドは処理結果をその後に伝えるために使う
結果の値を渡す場合は、pipe
メソッドを使います。
1.tap(n => println(s"piped $n")) .pipe(_ + 2) .tap(n => println(s"piped $n")) .pipe(_ * 3) .tap(n => println(s"piped $n"))
結果は以下のようになります。
piped 1 piped 3 piped 9
メソッドチェーンのメリット・デメリット
scala.util.chaining
パッケージは必ずしも使わなければならないものではありません。
メソッドチェーンはひとつのコーディングスタイルであり、 したがってこのパッケージは必要に応じて使用するものといえます。
以下のメリット・デメリットを踏まえて、使うかどうかを判断しましょう。
メソッドチェーンのメリットは、処理の流れを明確にできること
メソッドチェーンは、その書き方の特性上、処理の流れ、順番が明確になります。 非同期処理を挟まない限り、前から後ろに向けて順番に処理されることが期待できます。
処理がメソッドチェーンの各スコープの中で完結するため、一時変数を定義したり命名する手間が省けます。 するにしても、スコープを限定できるので不用意に変数へアクセスすることを防ぐことができます。
メソッドチェーンのデメリット1:複雑な流れや分岐を表現できない。
メソッドチェーンは処理の流れを明確にしやすいのがメリットでした。
ただし、途中で分岐するような処理は表現できません。
そのような場合はメソッドチェーンを使うのを諦めるとよいでしょう。
メソッドチェーンのデメリット2:非同期処理に向かない。
あるいは、前から順番に処理されるように見えることが誤解を生んでしまう場面もあります。
メソッドチェーン内で非同期処理を実行していて、実際にはどの順番で行われるかが定かでない場合には、 読み手が誤解してしまい、あたかも前から順番に処理されると思いこんでしまう可能性があります。
そのような場合には、メソッドチェーンを使うことは避けましょう。
メソッドチェーンのデメリット3:コンパイル時間に影響を及ぼす「可能性」
これはメソッドチェーンそのもの問題ではなく、Scalaにおける実装上の問題です。
scala.util.chaining
パッケージではメソッドチェーンを実現するための実装に暗黙の型変換を使用しているので、
原理的にはコンパイル時間に悪影響を及ぼす可能性があります。
あくまで「可能性」というのがポイントです。
限定的に用いるのみであれば、全く問題はないでしょう。
大量に使った場合において許容範囲内におさまるかどうかは、プロジェクトによります。
scala.util.chaining
を頻用する場合にはこの点について留意しておき、コンパイル時間が気になってきた際には必要に応じて計測するなどして随時再検討しましょう。
その他のトピック
util.chaining._
をインポートするとtap/pipeメソッドが使えるようになるのは「暗黙の型変換」が働くから
なぜ scala.util.chaining
の中身をインポートするとtap/pipeメソッドを使えるようになるのでしょうか。
その理由は「暗黙の型変換」にあります。
暗黙の型変換については別の記事にて詳しく解説しています。
まず、scala.util.chaining
には暗黙の型変換を引き起こすscalaUtilChainingOps
というメソッドが含まれています。
scala.util.chaining
これがそのメソッドです。
implicit final def scalaUtilChainingOps[A](a: A): ChainingOps[A]
このメソッドはA
という型を受け取って、ChainingOps[A]
という型に変換するメソッドです。
scala.util.ChainingOps
ChainingOps
型について見てみると、このChainingOps
がtap
やpipe
メソッドを持っていたことがわかります。
前述の例で、単なるInt
であるはずの1
がtap
やpipe
メソッドを持っていたのは、
コンパイラがInt
からChainingOps[Int]
へとこっそり型を変換してくれていたからなのです。
Dotty (Scala 3)ではバージョン0.18から使える
Dotty (Scala 3)では、バージョン0.18からscala.util.chaining
パッケージを使うことができます。
Dotty 0.17までの標準ライブラリはScala 2.12のものだったため使用できませんでしたが、 Dotty0.18からはScala 2.13の標準ライブラリへと切り替えられたため、メソッドチェーンを使用できるようになりました。
コンパイラオプション-Yimports
を使ってインポート不要で使えるようにする
コンパイラオプション-Yimports
にてscala.util.chaining
を追加指定すると、
コードにおいて明示的にscala.util.chaining
パッケージをインポートすることなく、
tap
やpipe
メソッドを使うことができるようになります。
前述のとおり、scala.util.chaining
パッケージの実装上、暗黙の型変換を使用するので、
原理的にはコンパイル時間に悪影響を及ぼす可能性があるという点に留意しておきましょう。