以下のやりとりなどをして、なんとなく書きたくなったので解説してみます。
@gakuzzzz implicitついてないし、型クラスのインスタンスではなく、「高階レベルの関数」ですね。Id型使いたいだけなら、それ使っていいと思います(なにに使うのか・・・?)
2013-09-11 16:07:49 via web to @gakuzzzz
↓こっちは別のやりとり
まず問題です、scala.util.Try[A]
をscala.Either[Throwable, A]
に変換するメソッドは、どのように書けるでしょうか?(Aは任意のA型)
たとえば、以下のような"メソッド"として書けますね
import scala.util._ def try2either[A](t: Try[A]): Either[Throwable, A] = t match { case Success(s) => Right(s) case Failure(e) => Left(e) }
では、これをvalで束縛するにはどうすればいいでしょう*1
Scalaにおいて、valは多相になれないので、このようなものを作るためだけに、新しいオブジェクトを一つ生成しないといけません。ここで、ScalazのNaturalTransformationの出番です。NaturalTransformationを使うと以下のようになります。
// new NaturalTransformation[Try, ({type l[a]=Either[Throwable, a]})#l] { と等価 val try2either = new (Try ~> ({type l[a]=Either[Throwable, a]})#l){ def apply[A](t: Try[A]) = t match{ case Success(s) => Right(s) case Failure(e) => Left(e) } }
~>とか、type l[a] のあたりがとても気持ち悪くていいかんじですね。~>という変な矢印は、NaturalTransformationのaliasです。また、型引数を2つとるものは中置記法でかけるので、あんな気持ち悪い書き方ができます。
さて、こんな気持ち悪い冗長なやり方でvalで定義して、果たして使い道があるのでしょうか?
(他にもあるかもしれませんが)自分が思いつく例を2つあげてみましょう。
1つめは、「綺麗に合成ができる」です。
まず、先ほどのtry2eitherの逆関数を、NaturalTransformationを使って定義してみましょう。
val either2try = new (({type l[a]=Either[Throwable, a]})#l ~> Try){ def apply[A](t: Either[Throwable, A]) = t match{ case Right(s) => Success(s) case Left(e) => Failure(e) } }
すると、ScalazのNaturalTransformationには、最初からcomposeが定義されているので、以下のように簡単に合成できます。
either2try compose try2either
2つめは、「抽象化した、任意のNaturalTransformertion型を受け取るメソッド」というのが書けるようになります。たとえば、ScalazのStreamTには
def trans[N[+_]](t: M ~> N)(implicit M: Monad[M], N: Functor[N]): StreamT[N, A]
というシグネチャのメソッドがあります。
https://github.com/scalaz/scalaz/blob/v7.0.3/core/src/main/scala/scalaz/StreamT.scala#L29
これは、NaturalTransformertionのような抽象化した型を用意しておかないと、書けないメソッドですね。
さて、なぜかNaturalTransformertionを紹介しましたが、実際そんなに多く使いどころがあるわけではないので、普通のScala使いはおろか、Scalazを覚えたい人も、べつにこれは最初に理解する必要はないと思います。
ところで、sbtにも同じく~>という名前で定義されていて、実際にsbt内部で結構使われています
https://github.com/sbt/sbt/blob/v0.13.0/util/collection/src/main/scala/sbt/TypeFunctions.scala#L37