Scalaz の Functor trait を初心者が実装してみた

Scalaz Advent Calendar の 10 日目の記事として書いています.

前回の記事 では Functor の構造について数学の定義を対比させながら書きました. 今日は Scalaz を使ったプログラムを書いて Functor の実装をしてみましょう.

準備

今回の実装をするために sbt で管理している Scala プロジェクトを作成しました. 同じ環境を作成して色々触ってみると楽しいと思います. 実際, 私は自分で実装してみて Functor の使い方がより分かるようになりました.

私は以下の環境で作業しました. (scala, scalaz は sbt で取得するので, sbt のバージョンさえ合っていれば同じ作業ができるはずです)

sbt 0.12.1
scala 2.10.0-M7
scalaz 7.0.0-M3
Giter8 0.4.5

Giter8 についてはこちらを参照してください. http://blog.twiwt.org/e/f12c0f

前置き

説明を簡単にするために implicit なものは使っていません. 対象を函手で写すときには, implicit conversion を使うと綺麗に書けるのですが, 今回はそこが本題ではないので敢えて使っていません.

今回は以下のページを参考にしました.

環境準備

g8 typesafehub/scala-sbt コマンドを実行して, パッケージ名など適宜答えていきましょう. scala のバージョンを聞かれたら 2.10.0-M7 としておいてください.

<project_root>/project/<project_name>.scala に sbt の設定を追加して, scalaz が利用できるようにします.

resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",

libraryDependencies ++= Seq(
  "org.scalaz" % "scalaz-core" % "7.0.0-M3" cross CrossVersion.full
),

scalacOptions += "-feature",

initialCommands in console := "import scalaz._, Scalaz._"

実際のファイルは https://bitbucket.org/cocoatomo/scalazsample/src/e5efd3d7b750/project/ScalazsampleBuild.scala?at=default を見てください.

次に <project_root>/src/main/scala/<package_hierarchy>/なんとか.scala に以下の import 文を追加します.

import scalaz.Functor

ソースコードは https://bitbucket.org/cocoatomo/scalazsample/src/a451998f4f2fa9ab09771622a333e9dd9fbe64b9/src/main/scala/net/elliptium/Scalazsample.scala?at=default を見てください.

そして最後にコンソールに戻って sbt run コマンドを実行すると, scalaz-core の取得が行われ, プログラムが動きます. g8 コマンドの引数に typesafehub/scala-sbt を指定してプロジェクトを作成した場合, "Hello, <project_name>" メッセージが出力されるだけのプログラムになっているはずです.

作成する Functor

今回作成する Functor は Important という名前にします. 普通の「クラスの圏」から「重要なクラスの圏」への函手というイメージになります. 今回持ち上げる射の例として length というものを選びます. 図式を書くと以下のようになります.

              lift(length)
Important[String] → Important[Int]
       ↑                ↑
       ↑                ↑
       ↑                ↑
     String       →     Int
                length

length: String => Int 函数は名前の通り「文字列の長さ」を返すものです. Important という Functor は「2重にする」函手です. 実体は Tuple2 に似た case class で, toString をちょっとイジってあります. これはソースコードを見た方が理解が早いでしょう.

実装

Important

これが Important[T] の実装です.

case class Important[T](val value: T) extends Product2[T, T] {

  def _1: T = value
  def _2: T = value

  override def toString: String = value.toString + ", " + value.toString

}

このクラスのコンストラクタで「普通のクラス T 」を「重要なクラス Important[T] 」に持ち上げています. 図式の以下の矢印にあたります.

Important[String]    Important[Int]
       ↑                ↑
       ↑                ↑
       ↑                ↑
     String              Int

実装自体は Tuple2 に似ています. case class にすることで, canEqual とかの実装の労力を省いています.

ImportantFunctor

Important[T] だけでは対象しか持ち上げられていないので, 射を持ち上げるための実装を行います.

射を持ち上げるための Important[Functor] 型は次のように実装します.

class ImportantFunctor[A, B] extends Functor[Important] {

  def map[A, B](fa: Important[A])(f: A => B): Important[B] = new Important[B](f(fa.value))

}

図式では以下の箇所にあたります.

              lift(length)
Important[String] → Important[Int]
                  ↑
                  ↑
                  ↑
     String       →     Int
                length

Functor trait は map メソッドだけ実装すれば良いのでしたね. これで Functor にあるメソッドが全て使えます.

残りのコードを実装して動作を見てみます. 詳細な実装は https://bitbucket.org/cocoatomo/scalazsample/src/a451998f4f2fa9ab09771622a333e9dd9fbe64b9/src/main/scala/net/elliptium/Scalazsample.scala?at=default#cl-39 を見てください.

以下のような出力になります.

3分間だけ待ってやる, 3分間だけ待ってやる
大事なことなので二回繰り返しました。
importantMessage にはちゃんと 2 つ値があります。 _1: "3分間だけ待ってやる", _2: "3分間だけ待ってやる"
ちなみにセリフの文字数は 10 文字でした。

importantLength: 10, 10
文字数も大事なので二回繰り返しました。

liftedLength でも同じ結果になります。
importantLength2: 10, 10

真面目な解説

「Important にする」というのは,「与えられた値をコピーしてペアにする」という変換です. これは専門用語では diagonal functor と言います. これが何なのか? 何の役に立つのか? とかは圏論の基礎でも参照してください.

余談

前回「associative じゃなくて composite なんでは?」と言ってた話は pull request 送ったら意外とあっさり取り込まれました. pull request 投げるのなんて気軽でいいんですね.

おわりに

前回は数学の話が主で実装の話が一切ありませんでしたが, 今回は実装の話を中心にしてみました. だいぶ具体的な話ができたと思っています. 本当は String => Important[String] という implicit conversion を用意しておくと, いちいち書かなくても自動で持ち上げられて便利です. 既存のクラス全般にプラグイン的にメソッドを追加したいときなどは便利なんじゃないでしょうか? (まだちょっと具体的な場面が思い付かず……)

明日は誰になるか分かりませんが, 私自身まだまだ調べて書きたいことはあるので, またきっとこの Advent Calendar の記事を書くことでしょう.

それでは.

Comments

blog comments powered by Disqus

Licenses