ScalaでF#の順次パイプライン

旧タイトル:パイプ処理みたいなScalaの記述


例えば、ある数を2倍する関数twiceがあるとする。
コレを2回実行しようとすると、scalaコードでは多分こう書く。

def twice(n: Int) = 2*n
val num = twice( twice(10) )

何だかあまり嬉しくない*1
twice twice 10 の様に書ければ嬉しいが、twiceがメソッドでないので、ドットの省略は出来ない。
2度だと良いが、3,4度と次々と変換を書けていこうとすると、とたんに面倒くさくなる。
全て処理が同じならばfoldLeftなりで繰り返せば良いが、複数の処理をかける場合はあまり嬉しくない。
そこで、パイプ処理みたいに順次関数を適用出来る仕組みを作ってみようと思う。


想定はこんな感じ。

val num = 10 |> twice |> twice !!

10を流して、twiceを適用し、その結果を更に流し…と来て、!!で流れを止める*2
関数が適応される順番は前から順なので、ネストした関数の中から読むと言う事をせずに、前から順に処理を追っていける。

この実現方法は次の通りである。

case class Value[A](v: A) {
  def |>[B](func: A => B) = Value(func(v))
  def !!() = v
}

implicit def toValue[T](any: T) = Value(any)


では、"10 |> twice |> twice !!"を例に仕組みを見てみよう。
まず、10(Int型)はメソッド|>を持たないので、この時点でimplicit conversionが働き、Value[Int](10)に変換される。
そして、メソッド|>が呼び出されるが、引数は1つなので.の省略が可能であり、次のtwiceを引数として受け取る。
メソッド|>はValueの生成時の値の型(ここではInt)を1つ取り型Bへ変換する関数--すなわち、Intを1つだけ受け取り、他の何かを返す関数--を引数として受け取る。twiceはIntを取り、Intを返す関数であるので、このメソッドの引数として正しい。
この中では受け取った関数を実行し、Value[B]、すなわち変換後の値を持つValue型を返している。


後はこの繰り返しである。
しかし、最後に|>を実行した場合、その結果はValueである。
そのため、その中味を取り出すために終端記号として!!を導入した。このメソッドによって、最後まで関数を適用した結果を得る事が出来るのである。


他の使い方としては、

def read(file: String): Iterable[String] = ...
def write(file: String)(data: () => Iterable[String]): Boolean = { ...

// 従来
write("filename"){ () => read("in") }

// |>を使った例
// () => Iterable[String]が必要なので、途中で変換している。
// また、部分適用を用いてwriteを1引数関数に変換している。
read("in") |> { it => ( () => it) } |> write("filename")_ !!

の様に使える。
データをどの様な順で扱うのかが分かり安くなり、私的には嬉しいと思う。

参考
  • implicit conversionのおいしい使い方

http://www.coins.tsukuba.ac.jp/~i021216/docs/Scala-implicit-conversion.pdf

*1:多分個人差はあります

*2:補足:第一引数の型を保持したまま!!などの終端記号を用いずに処理の終了をする方法を思いつかなかっただけであり、可能ならばない方が嬉しい。