LoginSignup
13
15

More than 5 years have passed since last update.

scalazのコードリーディングしたメモ

Posted at

ここらへんのエントリ読んだんだけど、いまいち分からなかったのでscalazのコード読んだ。のをまとめる。
OptionとFunctorに絞って調べたけど、多分それ以外も同じだと思う。
実行環境はscala v2.10.3, scalaz v7.0.6。偶然手元でIntelliJで開いてたプロジェクトのバージョンがそうだっただけで特にこのバージョン特有の何かがあるわけではない。

なぜsome(1)がSome(1)になるのか

import scalaz._
import Scalaz._
some(1) // Some(1)

定義元を調べると、ここ

  final def some[A](a: A): Option[A] = Some(a)

これは単純にimport Scalaz._した時に

package scalaz

object Scalaz
  extends StateFunctions        // Functions related to the state monad
  with syntax.ToTypeClassOps    // syntax associated with type classes
  with syntax.ToDataOps         // syntax associated with Scalaz data structures
  with std.AllInstances         // Type class instances for the standard library types
  with std.AllFunctions         // Functions related to standard library types
  with syntax.std.ToAllStdOps   // syntax associated with standard library types
  with IdInstances              // Identity type and instances

ここがimportされ、std.AllFunctions→OptionFunctionsとたどるとsomeの定義元にたどり着く。

このようなXxxFunctionstraitはグローバル関数っぽく使う関数が定義されているらしい。

1.someがなぜSome(1)になるのか。

import scalaz._
import Scalaz._
1.some // Some(1)

これの仕組み。
この.someの定義元をIDEで調べると、ここになる

trait OptionIdOps[A] extends Ops[A] {
  def some: Option[A] = Some(self)
}

なぜこれが呼び出されるのか?
前節と同じようにScalaz→syntax.std.ToAllStdOps→ToOptionIdOpsの順でたどると

trait ToOptionIdOps {
  implicit def ToOptionIdOps[A](a: A) = new OptionIdOps[A] { def self = a }
}

ここにimplicit conversionが定義されており、1new OptionIdOps[Int] { def self = 1 }に変換され、このオブジェクトがsomeメソッドを持ってるのでSome(1)になる。

このようなXxxIdOpsはimplicit conversionを利用して既存の型に生やすメソッドを定義している模様1ToXxxIdOpsがそのとき使うimplicit conversionを定義してる。多分OpsはOperationsの略。

なぜFunctor[Option].fpair(Option(1))がSome(1,1)になるのか

ここからが本番

import scalaz._
import Scalaz._
Functor[Option].fpair(Option(1)) // Some(1,1)

Functor[Option]

object Functor {
  @inline def apply[F[_]](implicit F: Functor[F]): Functor[F] = F

  ////

  ////
}

これのapplyとimplicitパラメータを省略した形2。なので、Fが返る。このFFunctor[Option]型のimplicitパラメータで、

  implicit val optionInstance = new Traverse[Option] with MonadPlus[Option] with Each[Option] with Index[Option] with Length[Option] with Cozip[Option] with Zip[Option] with Unzip[Option] with IsEmpty[Option] {

これ。TraverseFunctorをmix-inしているので、optionInstanceFunctor[Option]になる。これは今まで同様Scalaz→std.AllInstances→OptionInstancesの順にたどると辿り着く。

よって、Functor[Option].fpair(Option(1))optionInstance.fpair(Option(1))になる。

で、Functor traitに定義されているfpair

  def fpair[A](fa: F[A]): F[(A, A)] = map(fa)(a => (a, a))

が呼び出され、Option(1)を渡すとSome(1,1)になる。

なぜimplicitly[Functor[Option]].fpair(Option(1))がSome(1,1)になるのか

ここらへんを調べると、以下の2つの書き方が出てくる。

Functor[Option].fpair(Option(1))
implicitly[Functor[Option]].fpair(Option(1))

これはどちらも前節で出てきたoptionsInstanceを取得しているだけ。前者のほうはapplyの第一引数リストが(implicit F: Functor[F])になっていてoptionInstanceを取得していて、後者はこのimplicit parameterを直接取得している3。あとは前節と同じ仕組み。

なぜOption(1).fpairはSome(1,1)になるのか

import scalaz._
import Scalaz._
Option(1).fpair // Some(1,1)

Scalaz→ToTypeClassOps→ToFunctorOpsでたどると

  implicit def ToFunctorOps[F[_],A](v: F[A])(implicit F0: Functor[F]) =
    new FunctorOps[F,A] { def self = v; implicit def F: Functor[F] = F0 }

このimplicit conversionが定義されている。今、FOption, AIntだ。
で、implicit F0: Functor[F]は既に見たとおりoptionInstanceだ。
なので、Option(1).fpairnew FunctorOps[Option,Int] { def self = Option(1); implicit def F: Functor[Option] = optionInstance }.fpairになる。
で、fpairの定義は

  final def fpair: F[(A, A)] = F.fpair(self)

になっていて、つまりoptionInstance.fpair(Option(1))となる。これはもはや前節・前前節と同様なわけで、Some(1,1)になる。
このようにToXxxOpsXxxOpsへのimplicit conversionを定義している模様。

型クラス

前節の仕組みはFunctor+Optionだったが、例えばFunctor+Listでも同様のことが出来る。

import scalaz._
import Scalaz._
List(1).fpair // List((1,1))

また、この時実行されるFunctorまわりのコードはOptionの時と同じだ。違うものは、Optionの時optionInstanceだったのがlistInstanceになるだけだ。

  implicit val listInstance = new Traverse[List] with MonadPlus[List] with Each[List] with Index[List] with Length[List] with Zip[List] with Unzip[List] with IsEmpty[List] {
略

fpairのインタフェースを変えずに複数の型で動くようになっている。このようにimplicit conversionを駆使して多相性を実現するテクニックは、Haskellの型クラスの機能をエミュレートしているためscalaでも単に型クラスと言うらしい。
以下のページが分かりやすかった。

参考


  1. このようなテクニックをメソッド注入とかpimp my libraryとかenrich my libraryとか静的モンキーパッチとか言うらしい。 

  2. Functorとだけ書くとFunctorオブジェクトを指すが、Functor[Option]のように型パラメータをつけるとapplyの省略形になる。最初これに気づかず混乱した。 

  3. implicitlyはscala.Predefにある関数でimplicitな値を取得する関数。 

13
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
15