2012年12月18日火曜日

Scalazのかきかた


というわけで、Scalaz Advent Calendarの18日目の記事です。

Scalazには多くの型クラスとそのインスタンスが定義されており、それを扱うために、多くの記法が存在します。
この記事では簡単で冗長な記法から、複雑で簡素な記法まで紹介していきます。

インスタンスの取得


implicit valueとして定義されている型クラスのインスタンスは、implicit parameterにより取得が可能です。

そのインスタンスの取得にも様々方法があります。

implicitly

Scala標準ライブラリに定義されている、implicit valueを取得する関数です。

import std.option._
val map = Map('foo -> "bar", 'hoge -> "fuga")
implicitly[Apply[Option]].apply2(map get 'foo, map get 'hoge)(_ + _)

TypeClass

また、Scalazの型クラスにはインスタンスの取得のための関数、TypeClass.applyを使用することができます。

Apply[Option].apply2(map get 'foo, map get 'hoge)(_ + _)
view raw apply.scala hosted with ❤ by GitHub

関数呼び出し


ある型クラスの関数を使用するとき、先ほどのようにインスタンスから直接呼び出すことができます。

import std.list._
Bind[List].join(List(List(1, 2), List(3, 4)))
view raw join.scala hosted with ❤ by GitHub

Ops

しかし、明示的に型クラスのインスタンスを取得するのは冗長です。
Scalazではimplicit conversionを用いて、型クラスのインスタンスをもつオブジェクトに対して暗黙の型変換を提供します。

import scalaz.syntax.bind._
List(List(1, 2), List(3, 4)).join
view raw ops.scala hosted with ❤ by GitHub

暗黙型変換の他にも、単一のオブジェクトを対象としない型クラスの関数がインポートされます。

import scalaz.std.anyVal._
Monoid[Int].zero
import scalaz.syntax.monoid._
mzero[Int]
view raw zero.scala hosted with ❤ by GitHub

関数定義


関数を定義するとき、implicit parameterを指定する方法が2つあります。

implicit

1つはimplicitを使う方法です。

def double[A](a: A)(implicit A: Semigroup[A]) =
A.append(a, a)
view raw double.scala hosted with ❤ by GitHub

Context Bounds

もう1つ、Context Boundsというものが存在します。

def doubleCB[A: Semigroup](a: A) =
Semigroup[A].append(a, a)

インスタンスを明示的に扱わない場合はContex Boundsで定義した方が良いでしょう。

def doubleCBS[A: Semigroup](a: A) = a |+| a

Syntax


大抵の場合の場合はOpsとContex Boundsで短いコードが得られますが、これらを使っても冗長になる場合があります。

def zeroTrio[A: Monoid] =
(mzero[A], mzero[A], mzero[A])
import scalaz.syntax.pointed._
def nestPoint[M[_]: Pointed, A](a: A) =
a.point[M].point[M]
view raw morph.scala hosted with ❤ by GitHub

Scalaでは返り値の型を指定してもimplicit valueを決定することは出来ないので明示的に型を書く必要があります。
このような場合、Syntaxを使うことで明示的に型を指定する必要がなくなります。

def zeroTrioS[A](implicit A: Monoid[A]) = {
import A.monoidSyntax._
(mzero, mzero, mzero)
}
def nestPointS[M[_], A](a: A)(implicit M: Pointed[M]) = {
import M.pointedSyntax._
point(point(a))
}
view raw syntax.scala hosted with ❤ by GitHub

まとめ


短く簡素なコードになるほど、複雑な仕組みが使われていきます。
大抵のものはContex BoundsとOpsで短いコードになるので、積極的に使っていきましょう。
型クラスのインスタンスから直接関数を呼ぶのも良いですが、Syntaxをimportした方が簡素になる場合があることも頭に入れておくと良いでしょう。

0 件のコメント:

コメントを投稿