Hatena::Groupocaml-nagoya

yoshihiro503の関数的日記

2011-03-06 (Sun)

Scalaでパイプライン演算子

| 16:08 | Scalaでパイプライン演算子 - yoshihiro503の関数的日記 を含むブックマーク はてなブックマーク - Scalaでパイプライン演算子 - yoshihiro503の関数的日記

Scalaを書いているとF#のようなパイプライン演算子が欲しくなることがよくあります。例えば次のような場合です

欲しくなる例1: 整数とかのパースをOptionで安全に行いたいケース

Scala標準の String#toInt は失敗した時に例外を発生してしまいますが、例外ではなくOptionで扱いたくなりました。こんなときはきっと次のような関数定義しますよね。

def parseInt(s: String): Option[Int] =
  try {
    Some(s.toInt)
  } catch {
    case _ => None
  }

でもこれは関数であって文字列のメンバではないので、この関数を使用するとき処理の流れがわかりにくくなることがあります

例えば「ファイルから一行読んで → 整数パースして → 失敗した場合は0とする」という一連の処理は以下のようになってしまいます。処理の流れとプログラム微妙に合ってないですね。

parseInt(file.getLine()) getOrElse(0)

ここでパイプライン演算子を使うと以下のように書くことができます

file getLine() |> parseInt getOrElse(0)

実行した処理の順番に関数記述できてハッピーになりました

欲しくなる例2: Intの配列に対して平均をとるaverage操作定義したい。

Array[Int]型の値に対して平均値をとる操作が頻繁に現れるプログラムを書いていたとします。

このとき次の関数定義して使うとします。

def average(xs: Array[Int]): Double = xs.foldLeft(0.0)(_+_) / xs.length

ここまでは普通自然だと思いますが、これを使うときパイプライン演算子を使うとまるでaverageがArray[Int]のメンバであるかのように自然処理を連ねることができます

Array(1,2,5,5,9,0) |> average floor

種明かし(パイプライン演算子定義)

このようなパイプライン演算子は実は次のように定義できます

クラス名や関数名はテキトーです。すいません。

class A[T](val v: T) { def |>[S] (f: T => S): S = f(v)}
implicit def AofT[T] (x: T): A[T] = new A(x)

みなさんもぜひ使って見てください。楽しくね!

[追記: 2011 03/07]

Scala練習場でも使って見ました

http://nagoyascala.appspot.com/bbs/thread/52001

あと、id:osiireさんに言われたのですが、これだとAクラスというクラス名が予約されてしまうので、次のように一行で書いた方がより実用的かもしれません。

implicit def AofT[T](x: T) = new { def |>[S] (f: T => S): S = f(x) }