Start Scalaz!

会場案内とか?

発表者

  • よしださんしろう(halcat0x15a)
  • Scala, Clojure, Haskellとか
  • Scalaは2.5年くらい書いてます
  • Scalazは1年くらい
  • 大学生になりました
  • 夏休みです

今日の内容

  • Type Class
  • Scalaz7
scalaVersion := "2.10.0-M6"

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

libraryDependencies ++= Seq(
  "org.scalaz" %% "scalaz-core" % "7.0.0-M1"
)

scalacOptions += "-feature"

initialCommands in console := "import scalaz._, Scalaz._"
  • 発表中のツッコミは大歓迎
  • わからないところがあったら聞いて下さい
  • 適当に休憩をとります

Type Class

Scalazの例

ある値を2倍する

def double[A](a: A)(implicit s: Semigroup[A]) = s.append(a, a)

double(2) assert_=== 4
double("2") assert_=== "22"

Semigroup

appendは抽象メソッド、2つの値を結合する関数

Pointを例にインスタンスを定義する

case class Point(x: Int, y: Int) {
  def +(p: Point) = Point(x + p.x, y + p.y)
}

object Point {
  implicit object PointInstance extends Semigroup[Point] {
    def append(p1: Point, p2: => Point) = p1 + p2
  }
}

Semigroup[Point]のインスタンスが暗黙的に渡される

assert(double(Point(1, 2)) == Point(2, 4))

implicit parameter

スコープ内のimplicit valueだけでなく、コンパニオンオブジェクトに定義されたimplicit valueも探索される

Law

Lawは型クラス内にtraitとして定義されてる

appendの定義はSemigroupLawを満たしていなければならない

  • append(f1, append(f2, f3)) == append(append(f1, f2), f3)

Show

文字列へ変換する関数を定義する

インスタンスを複数定義する時はmix-inや、複数のimplicit valueを定義する

object Point {
  implicit object PointInstance extends Show[Point] with Semigroup[Point] {
    def show(p: Point) = p.toString.toList
    def append(p1: Point, p2: => Point) = p1 + p2
  }
}

演習

  • Vectorに対するShowのインスタンス
  • 以下のクラスに対するShowとSemigroupのインスタンス
case class Rational(n: Int, d: Int) {
  def +(r: Rational) = Rational(n * r.d + r.n * d, d * r.d)
  override def toString = s"$n/$d"
}

object vector {
  def VectorShow[A]: Show[Vector[A]]
}

import vector._
assert(implicitly[Show[Vector[Int]]].shows(Vector(1)) == "Vector(1)")
assert(implicitly[Show[Vector[String]]].shows(Vector("geso")) == "Vector(geso)")
assert(implicitly[Show[Rational]].shows(Rational(1, 2)) == "1/2")
assert(implicitly[Semigroup[Rational]].append(Rational(1, 2), Rational(1, 2)) == Rational(4, 4))

Context Bound

型クラスを利用するとき、明示的にimplicit parameterを書かないことが多い

次の関数は同じ動作をする

def quote[A](a: A)(implicit s: Show[A]) = s.show(a).mkString("'", "", "'")
def quote[A: Show](a: A) = implicitly[Show[A]].show(a).mkString("'", "", "'")

quote("geso") assert_=== "'geso'"
quote(List(1, 2, 3)) assert_=== "'[1,2,3]'"

Scalaz

パッケージ

  • scalaz

    • 型クラスとデータ型
  • scalaz.std

    • 標準ライブラリに対する型クラスのインスタンス
  • scalaz.syntax

    • シンタックス

命名規則

  • Instances

    • データ型に対する型クラスのインスタンス
  • Functions

    • データ型に関係した関数
  • Ops

    • 型クラスに関係したメソッド
  • Syntax

    • 暗黙の型変換

型クラスのコンパニオンオブジェクト

  • 型クラスに関係した関数
  • applyはインスタンスを得る
  • インスタンスを定義するための関数
def double[A: Semigroup](a: A) = Semigroup[A].append(a, a)

implicit lazy val PointShow = Show.showA[Point]
implicit lazy val PointEqual = Equal.equalA[Point]

データ型のコンパニオンオブジェクト

InstancesとFunctionsを継承している

Syntax

先の例は次の様に書ける

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

|+|

SemigroupOpsに定義されてるメソッド

Semigroupのインスタンスを持つ型に対して暗黙の型変換がされる

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

scalaz.syntax

インスタンスが存在すればOpsで定義されたメソッドが使える

主要な関数のほとんどはシンタックスが定義されている

def quote[A: Show](a: A) = Show[A].show(a).mkString("'", "", "'")
def quote[A: Show](a: A) = a.show.mkString("'", "", "'")

Semigroupに恒等元を加えたもの

Rationalの例

case class Rational(n: Int, d: Int) {
  def +(r: Rational) = Rational(n * r.d + r.n * d, d * r.d)
}

object Rational {
  implicit object RationalInstance extends Monoid[Rational] {
    def zero = Rational(0, 1)
    def append(r1: Rational, r2: => Rational) = r1 + r2
  }
}

mzero[Int] assert_=== 0
mzero[Option[String]] assert_=== None
mzero[Rational] assert_=== Rational(0, 1)

MonoidLaw

恒等元の性質

  • append(zero, a) == a
  • append(a, zero) == a
val a = 1
mzero[Int] |+| a assert_=== a
a |+| mzero[Int] assert_=== a

Monoid.replicate

任意の回数繰り返し関数を適用し、結果を集める

Monoid.replicate[List, Int](0)(3, 1 +) assert_=== List(0, 1, 2)

Monoid.unfold

Noneを返すまで繰り返し関数を適用し、結果を集める

Monoid.unfold[List, List[Int], Int](List(1, 2, 3)) {
  case Nil => None
  case x :: xs => Some(x * 2 -> xs)
} assert_=== List(2, 4, 6)

問題

  • replicateを用いて偶数列からn個取得する関数evens
  • unfoldを用いて10進数から2進数へ変換する関数encode
def evens(n: Int): List[Int]
def encode(n: Int): List[Int]
evens(5) assert_=== List(0, 2, 4, 6, 8)
encode(13) assert_=== List(1, 0, 1, 1)

Group

逆元を持つMonoid

object Rational {
  implicit object RationalInstance extends Order[Rational] with Show[Rational] with Group[Rational] {
    def zero = Rational(0, 1)
    def append(r1: Rational, r2: => Rational) = r1 + r2
    def inverse(r: Rational) = Rational(-r.n, r.d)
  }
}

1.inverse assert_=== -1
Rational(1, 2).inverse assert_=== Rational(-1, 2)

Plus, PlusEmpty

量化されたSemigroup, Monoid

Plusは要素の性質に依存しない

List(1, 2) |+| List(3, 4) assert_=== List(1, 2, 3, 4)
List(1, 2) <+> List(3, 4) assert_=== List(1, 2, 3, 4)
Option(1) |+| Option(1) assert_=== Option(2)
Option(1) <+> Option(1) assert_=== Option(1)

object vector {
  implicit object VectorInstance extends PlusEmpty[Vector] {
    def empty[A] = Vector.empty[A]
    def plus[A](v1: Vector[A], v2: => Vector[A]) = v1 ++ v2
  }
}
import vector._
assert(Vector(1, 2) <+> Vector(3, 4) == Vector(1, 2, 3, 4))

恒等モナド

("geso": Id[String]) assert_=== "geso"

IdOps

全ての型に対してシンタックスが供給される

some, none, left, right

OptionやEitherのインスタンスを生成する

1.some assert_=== Some(1)
none[Int] assert_=== None
assert(1.right[String] === Right(1))
assert("geso".left[Int] === Left("geso"))

|>

関数に自身を適用する

1 |> (1 +) assert_=== 2
"geso" |> (_.size) assert_=== 4
0 |> Show[Int].shows assert_=== "0"

Order

Equal

等価性

object Point {
  implicit object PointInstance extends Equal[Point] {
    def equal(p1: Point, p2: Point) = p1 == p2
  }
}

assert(Point(2, 3) === Point(2, 3))
assert(Point(2, 3) =/= Point(3, 5))

型安全な演算

Scalazの関数はジェネリクスを利用している

1 == "geso"
/* 1 === "geso" */ // compile error

1 + 1.5
/* 1 |+| 1.5 */ // compile error

Order

順序

object Rational {
  implicit object RationalInstance extends Order[Rational] {
    def order(r1: Rational, r2: Rational) = r1.n * r2.d -> r2.n * r1.d match {
      case (m, n) if m == n => Ordering.EQ
      case (m, n) if m < n => Ordering.LT
      case (m, n) if m > n => Ordering.GT
    }
  }
}

assert(Rational(1, 2) === Rational(1, 2))
assert(Rational(1, 2) < Rational(3, 4))
assert(Rational(5, 2) >= Rational(5, 3))

Ordering

Javaのcompareが返す-1、0、1に対応する

Orderingはモノイドであるため、複数の比較結果を結合することができる

mzero[Ordering] assert_=== Ordering.EQ
(Ordering.EQ: Ordering) |+| Ordering.LT assert_=== Ordering.LT
(Ordering.GT: Ordering) |+| Ordering.LT assert_=== Ordering.GT
(Ordering.GT: Ordering) |+| Ordering.EQ assert_=== Ordering.GT

Orderingを利用したsort

case class Person(name: String, age: Int, height: Int)

object Person {
  implicit object PersonInstance extends Show[Person] with Order[Person] {
    def show(p: Person) = p.toString.toList
    def order(p1: Person, p2: Person) =
      p1.age ?|? p2.age |+| p1.height ?|? p2.height
  }
}

val miku = Person("miku", 16, 158)
val rin = Person("rin", 14, 152)
val len = Person("len", 14, 156)
List(miku, rin, len) sorted Order[Person].toScalaOrdering assert_=== List(rin, len, miku)

演習

  • java.util.Dateに対するOrderのインスタンス
  • 以下のクラスに対するOrderのインスタンス

    • gradeとbirthdayを用いる
    • ただしgrade重きを置く
case class Student(name: String, grade: Int, birthday: Date)

val format = new java.text.SimpleDateFormat("yyyy MM dd")
val akari = Student("akari", 1, format.parse("1995 07 24"))
val kyoko = Student("kyoko", 2, format.parse("1995 03 28"))
val yui = Student("yui", 2, format.parse("1994 04 22"))
val chinatsu = Student("chinatsu", 1, format.parse("1995 11 06"))
List(akari, kyoko, yui, chinatsu) sorted Order[Student].toScalaOrdering assert_=== List(akari, chinatsu, yui, kyoko)

Enum

Orderにsuccessorとpredecessorを加えたもの

object Rational {
  implicit object RationalInstance extends Enum[Rational] {
    def order(r1: Rational, r2: Rational) = r1.n * r2.d -> r2.n * r1.d match {
      case (m, n) if m == n => Ordering.EQ
      case (m, n) if m < n => Ordering.LT
      case (m, n) if m > n => Ordering.GT
    }
    def succ(r: Rational) = r.copy(n = r.n + r.d)
    def pred(r: Rational) = r.copy(n = r.n - r.d)
  }
}

1.succ assert_=== 2
Rational(1, 2).succ assert_=== Rational(3, 2)
'b'.pred assert_=== 'a'
Rational(1, 2).pred assert_=== Rational(-1, 2)

Tagged Types

Tag

既存の型を別の型として定義する

sealed trait Author
sealed trait Title

case class Book(title: String @@ Title, author: String @@ Author)

val book = Book(Tag("Programming in Scala"), Tag("Martin Odersky"))
/* book.copy(title = book.author) */ // compile error

newtype

Scalaz6ではPimp my Library Patternが用いられてる

Scalaz7ではTagged Typesを用いる

import scalaz.Tags._
3 |+| 3 assert_=== 6
(Multiplication(3) |+| Multiplication(3): Int) assert_=== 9
(Conjunction(true) |+| Conjunction(false): Boolean) assert_=== false
(Disjunction(true) |+| Disjunction(false): Boolean) assert_=== true
import scalaz.Dual._
(Dual("hello") |+| Dual("world"): String) assert_=== "worldhello"

UnionTypes

Eitherとは違い、コンテナで包む必要がない

def size[A](a: A)(implicit ev: A Contains t[Int]#t[String]#t[List[_]]) = a match {
  case i: Int => i
  case s: String => s.length
  case l: List[_] => l.size
}
size(1) assert_=== 1
size("geso") assert_=== 4
size(List(1, 2, 3)) assert_=== 3
/* size(1L) */ // compile error

Monad

Higher Kinds

型をパラメータとしてとる型

import scala.language.higherKinds
def triple[F[_]: Plus, A](fa: F[A]) = fa <+> fa <+> fa
triple(Option(1)) assert_=== Option(1)
triple(List(1)) assert_=== List(1, 1, 1)

Functor

map

関数をコンテナに適用する

object vector {
  implicit object VectorInstance extends Functor[Vector] {
    def map[A, B](v: Vector[A])(f: A => B) = v map f
  }
}

def fdouble[F[_]: Functor, A: Semigroup](fa: F[A]) = fa.map(a => a |+| a)
fdouble(List(1, 2, 3)) assert_=== List(2, 4, 6)
fdouble("geso".some) assert_=== Some("gesogeso")
import vector._
fdouble(Vector(1.2, 2.1)) assert_=== Vector(2.4, 4.2)

FunctorLaw

mapの性質

  • map(fa)(x => x) == fa
  • map(map(fa)(f))(g) == map(fa)(g compose f)
val fa = List(1, 2)
lazy val f: Int => Int = _ + 2
lazy val g: Int => Int = _ * 2
fa map (x => x) assert_=== fa
fa map f map g assert_=== (fa map g <<< f)

Pointed

point

コンテナを構築する

object vector {
  implicit object VectorInstance extends Pointed[Vector] {
    def map[A, B](v: Vector[A])(f: A => B) = v map f
    def point[A](a: => A) = Vector(a)
  }
}

Pointed[List].point(1) assert_=== List(1)
Pointed[Option].point(1) assert_=== Some(1)
import vector._
Pointed[Vector].point(1) assert_=== Vector(1)

型の部分適用

assert(Functor[({ type F[A] = Either[String, A] })#F].map(Right(1))(_.succ) === Right(2))
assert(Pointed[({ type F[A] = Either[String, A] })#F].point(1) === Right(1))

Apply

ap

持ち上げられた関数をコンテナに適用し、新しいコンテナを構築する

object vector {
  implicit object VectorInstance extends Apply[Vector] {
    def map[A, B](v: Vector[A])(f: A => B) = v map f
    def ap[A, B](va: => Vector[A])(vab: => Vector[A => B]) = vab flatMap (va map _)
  }
}

Option(0) <*> Option(Enum[Int].succ _) assert_=== Option(1)
List(1, 2, 3) <*> PlusEmpty[List].empty[Int => Int] assert_=== Nil
import vector._
Vector(1, 2) <*> Vector(Enum[Int].succ _, Enum[Int].pred _) assert_===  Vector(2, 3, 0, 1)

Applicative

ApplyとPointedを組み合わせたもの

object vector {
  implicit object VectorInstance extends Applicative[Vector] {
    def point[A](a: => A) = Vector(a)
    def ap[A, B](va: => Vector[A])(vab: => Vector[A => B]) = vab flatMap (va map _)
  }
}

ApplicativeLaw

  • ap(fa)(point((a: A) => a)) == fa
  • ap(ap(fa)(fab))(fbc) == ap(fa)(ap(fab)(ap(fbc)(point((bc: B => C) => (ab: A => B) => bc compose ab))))
  • ap(point(a))(point(ab)) == point(ab(a))
  • ap(point(a))(fab) == ap(fab)(point((f: A => B) => f(a)))
val a = 0
val fa = Option(a)
lazy val fab: Option[Int => String] = Option(_.toString)
lazy val fbc: Option[String => Int] = Option(_.size)
fa <*> ((a: Int) => a).point[Option] assert_=== fa
fa <*> fab <*> fbc assert_=== fa <*> (fab <*> (fbc <*> (((bc: String => Int) => (ab: Int => String) => bc compose ab).point[Option])))
a.point[Option] <*> ab.point[Option] assert_=== ab(a).point[Option]
a.point[Option] <*> fab assert_=== fab <*> ((f: Int => String) => f(a)).point[Option]

Applicative Style

ApplicativeBuilderを用いて計算を構築する

def append3[F[_]: Apply, A: Semigroup](fa: F[A], fb: F[A], fc: F[A]) =
  (fa |@| fb |@| fc)(_ |+| _ |+| _)
append3(Option(1), Option(2), Option(3)) assert_=== Option(6)
append3(Option(1), None, Option(3)) assert_=== None
append3(List(1), List(1, 2), List(1, 2, 3)) assert_=== List(3, 4, 5, 4, 5, 6)

演習

  • Map[String, String]から“id“と“pass“をキーとして値を取り出しUserを構築する
case class User(id: String, pass: String)
def user(m: Map[String, String]): Option[User]

user(Map("id" -> "halcat0x15a", "pass" -> "gesogeso")) assert_=== Some(User("halcat0x15a", "gesogeso"))
user(Map.empty) assert_=== None

Bind

bind

関数をコンテナに適用し、新しいコンテナを構築する

object vector {
  implicit object VectorInstance extends Bind[Vector] {
    def map[A, B](v: Vector[A])(f: A => B) = v map f
    def bind[A, B](v: Vector[A])(f: A => Vector[B]) = v flatMap f
  }
}

def append3[F[_]: Bind, A: Semigroup](fa: F[A], fb: F[A], fc: F[A]) =
  for {
    a <- fa
    b <- fb
    c <- fc
  } yield a |+| b |+| c

for式

map, flatMap, filterに変換される

(for (a <- List(1, 2)) yield a + 1) assert_=== List(1, 2).map(a => a + 1)
(for (a <- Option(1); b <- Option(2)) yield a + b) assert_=== Option(1).flatMap(a => Option(2).map(b => a + b))
(for (a <- List(1, 2) if a % 2 == 0) yield a) assert_=== List(1, 2).filter(a => a % 2 == 0)

Monad

ApplicativeとBindを組み合わせたもの

object vector {
  implicit object VectorInstance extends Monad[Vector] {
    def point[A](a: => A) = Vector(a)
    def bind[A, B](v: Vector[A])(f: A => Vector[B]) = v flatMap f
  }
}

MonadLaw

  • bind(fa)(point(_: A)) == fa
  • bind(point(a))(f) == f(a)
  • bind(bind(fa)(f))(g) == bind(fa)((a: A) => bind(f(a))(g))
import scala.util.control.Exception._
val a = 1
val fa = Option(a)
lazy val f: Int => Option[String] = _.toString |> Option.apply
lazy val g: String => Option[Int] = allCatch opt _.toInt
(fa >>= (_.point[Option])) assert_=== fa
(a.point[Option] >>= f) assert_=== f(a)
(fa >>= f >>= g) assert_=== (fa >>= (a => f(a) >>= g))

ApplicativePlus

ApplicativeとPlusEmptyを組み合わせたもの

object vector {
  implicit object VectorInstance extends ApplicativePlus[Vector] {
    def empty[A] = Vector.empty[A]
    def plus[A](v1: Vector[A], v2: => Vector[A]) = v1 ++ v2
    def point[A](a: => A) = Vector(a)
    def ap[A, B](va: => Vector[A])(vab: => Vector[A => B]) = vab flatMap (va map _)
  }
}

MonadPlus

MonadとApplicativePlusを組み合わせたもの

filterが定義される

object vector {
  implicit object VectorInstance extends MonadPlus[Vector] {
    def empty[A] = Vector.empty[A]
    def plus[A](v1: Vector[A], v2: => Vector[A]) = v1 ++ v2
    def point[A](a: => A) = Vector(a)
    def bind[A, B](v: Vector[A])(f: A => Vector[B]) = v flatMap f
  }
}

def evens[F[_]: MonadPlus](f: F[Int]) = f filter (_ % 2 === 0)
evens(List(1, 2, 3)) assert_=== List(2)
evens(Option(1)) assert_=== None
import vector._
evens(Vector(1, 2, 3)) assert_=== Vector(2)