Introduction of
Generic Programming
in Scala

@lyrical_logical

共通化、してますか?

OOP でありがちな共通化

メンバーの共通化

 

trait UserProfile {
  def name: String
  def age: Int
}

trait UserStatus {
  def name: String
  def stamina: Int
}

メンバーの共通化

 

trait UserProfile {
  def name: String
  def age: Int
}

trait UserStatus {
  def name: String
  def stamina: Int
}

メンバーの共通化

 

trait User {
  def name: String
}

trait UserProfile extends User {
  def age: Int
}

trait UserStatus extends User {
  def stamina: Int
}

単なる例なので正規化とか composition とか無視

FP でありがちな共通化

処理の共通化

 

sealed trait IntList
case object IntNil extends IntList
case class IntCase(head: Int, tail: IntList) extends IntList

def sum(list: IntList): Int = list match {
  case IntNil => 0
  case IntCons(head, tail) => head + sum(tail)
}

def product(list: IntList): Int = list match {
  case IntNil => 1
  case IntCons(head, tail) => head * product0(tail)
}

処理の共通化

 

sealed trait IntList
case object IntNil extends IntList
case class IntCons(head: Int, tail: IntList) extends IntList

def sum(list: IntList): Int = list match {
  case IntNil => 0
  case IntCons(head, tail) => head + sum(tail)
}

def product(list: IntList): Int = list match {
  case IntNil => 1
  case IntCons(head, tail) => head * product0(tail)
}

処理の共通化

 

sealed trait IntList
case object IntNil extends IntList
case class IntCons(head: Int, tail: IntList) extends IntList

def fold[T](list: IntList, t: T)(f: (Int, T) => T): T = {
  list match {
    case IntNil => t
    case IntCons(head, tail) => f(head, fold(tail, t)(f))
  }
}

def sum(list: IntList): Int = fold(list, 0) { _ + _ }

def prod(list: IntList): Int = fold(list, 1) { _ * _ }

ここから main topic

「データ構造」の共通化

 

sealed trait IntList
case object IntNil extends IntList
case class IntCons(head: Int, tail: IntList) extends IntList

sealed trait IntTree
case object IntLeaf extends IntTree
case class IntNode(value: Int, left: IntTree, right: IntTree)
  extends IntTree

「データ構造」の共通化

 

sealed trait IntList
case object IntNil extends IntList
case class IntCons(head: Int, tail: IntList) extends IntList

sealed trait IntTree
case object IntLeaf extends IntTree
case class IntNode(value: Int, left: IntTree, right: IntTree)
  extends IntTree

「データ構造」の共通化

 

sealed trait ListLike[F[+_]]
case object LikeNil extends ListLike[Nothing]
case class LikeCons[F[+_]](out: F[ListLike[F]])
  extends ListLike[F]

type IntList =
  ListLike[ ({ type apply[+T] = (Int, T) })#apply ]
type IntTree =
  ListLike[ ({ type apply[+T] = (Int, T, T) })#apply }

「データ構造」の共通化 その 2

 

sealed trait IntList
case object IntNil extends IntList
case class IntCons(head: Int, tail: IntList) extends IntList

sealed trait IntTree
case object IntLeaf extends IntTree
case class IntNode(value: Int, left: IntTree, right: IntTree)
  extends IntTree

case class IntRoseTree(children: Seq[IntRoseTree])

「データ構造」の共通化 その 2

 

 

sealed trait IntList
case object IntNil extends IntList
case class IntCons(head: Int, tail: IntList) extends IntList

sealed trait IntTree
case object IntLeaf extends IntTree
case class IntNode(value: Int, left: IntTree, right: IntTree)
  extends IntTree

case class IntRoseTree(children: Seq[IntRoseTree])

Nil, Leaf 相当が無い!

再帰に着目する

「データ構造」の共通化 その 2

 

 

sealed trait IntList
case object IntNil extends IntList
case class IntCons(head: Int, tail: IntList) extends IntList

sealed trait IntTree
case object IntLeaf extends IntTree
case class IntNode(value: Int, left: IntTree, right: IntTree)
  extends IntTree

case class IntRoseTree(children: Seq[IntRoseTree])

ただただ再帰する

 

 

 

case class Infinite(self: Infinite)

使い道 is ない

μ operator

 

 

 

case class Mu[F[+_]](in: F[Mu[F]])

構造に「遊び」を作る

F

Cons

μ

F

Node

F

RoseTree

F

Nil, Leaf

μ……

μ

μ

IntList

 

 

 

sealed trait IntListF[+T]
case object IntNilF extends IntListF[Nothing]
case class IntConsF[+T](head: Int, tail: T) extends IntListF[T]

type IntList = Mu[IntListF]
def nil = Mu[IntListF](IntNilF)
def cons(head: Int, tail: Mu[IntListF]) =
  Mu[IntListF](IntConsF(head, tail))
val nums: IntList = cons(1, cons(2, nil))

F

Cons

μ

F

Nil

再帰的構造に対する畳み込み

 

 

 

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

def reduce[T, F[+_]](mu: Mu[F])(f: F[T] => T)
                    (implicit ev: Functor[F]): T =
{
  f(ev.map(mu.in)(reduce[T, F](_)(f)))
}

Functor を経由して再帰的に処理

分岐 - 停止 or 再帰呼び出し

 

 

 

implicit object intListFunctor extends Functor[IntListF] {
  def map[A, B](fa: IntListF[A])(f: A => B): IntListF[B] = {
    fa match {
      case IntNilF => IntNilF
      case IntConsF(head, tail) => IntConsF(head, f(tail))
    }
  }
}

Functor を経由した再帰呼び出し

fold, sum, prod の実装

 

 

 

def fold[T](list: IntList, t: T)(f: (Int, T) => T): T = {
  reduce(list)({
    case IntNilF => t
    case IntConsF(head, tail) => f(head, tail)
  }: IntListF[T] => T)
}

def sum(list: IntList): Int = fold(intList, 0) { _ + _ }

def prod(list: IntList): Int = fold(intList, 1) { _ * _ }

Parametric generics

μ operator

case class Mu[F[+_, +_], +A](in: F[A, Mu[F, A]])

// non parametric version
// case class Mu[F[+_]](in: F[Mu[F]])

List

 

 

 

sealed trait ListF[+A, +B]
case object NilF extends ListF[Nothing, Nothing]
case class ConsF[+A, +B](head: A, tail: B) extends ListF[A, B]

type List[+A] = Mu[ListF, A]
def nil = Mu[ListF, Nothing](NilF)
def cons[A](head: A, tail: Mu[ListF, A]) = Mu(ConsF(head, tail))

val intList: List[Int] = cons(1, cons(2, nil))
val stringList: List[String] = cons("a", cons("b", nil))

Covariant Binary Functor

 

 

 

trait BiFunctor[F[_, _]] {
  def bimap[A, B, C, D](fab: F[A, B])
                       (f: A => C)
                       (g: B => D): F[C, D]
}

再帰的構造に対する畳み込み

 

 

 

trait BiFunctor[F[_, _]] {
  def bimap[A, B, C, D](fab: F[A, B])
                       (f: A => C)
                       (g: B => D): F[C, D]
}

def reduce[A, B, F[+_, +_]](mu: Mu[F, A])(f: F[A, B] => B)
                           (implicit ev: BiFunctor[F]): B =
{
  f(ev.bimap(mu.in)(identity)(reduce(_)(f)))
}

分岐 - 停止 or 再帰呼び出し

 

 

 

implicit object listBiFunctor extends BiFunctor[ListF] {
  def bimap[A, B, C, D](fab: ListF[A, B])
                       (f: A => C)
                       (g: B => D): ListF[C, D] = {
    fab match {
      case NilF => NilF
      case ConsF(head, tail) => ConsF(f(head), g(tail))
    }
  }
}

BiFunctor を経由した再帰呼び出し

fold, sum, prod の実装

 

 

 

def fold[A, B](list: List[A], t: B)(f: (A, B) => B): B = {
  reduce(list)({
    case NilF => t
    case ConsF(head, tail) => f(head, tail)
  }: ListF[A, B] => B)
}

def sum(list: List[Int]): Int = fold(list, 0) { _ + _ }

def prod(list: List[Int]): Int = fold(list, 1) { _ + _ }

まとめ

  • 共通化しよう
  • データ構造も共通化できるぞ
  • まあ普通はあんまりしない

話さなかったこと

  • datatype generics programming(DGP)
    • ​SYB, PolyP, etc...
  • type representation, universe

ところで

μ operator

case class Mu[F[+_, +_], +A](in: F[A, Mu[F, A]])

要素をもつ終端を導入

sealed trait Mu[F[+_, +_], +A]
case class End[[F+_, +_], +A](a: A) extends Mu[F, A]
case class In[F[+_, +_], +A](in: F[A, Mu[F, A]])
  extends Mu[F, A]

// ListLike っぽいですね

要素持つの終端だけにする

sealed trait Mu[F[+_], +A]
case class End[F[+_], +A](a: A) extends Mu[F, A]
case class In[F[+_], +A](in: F[Mu[F, A]])
  extends Mu[F, A]

// 最初にでてきたμ operator っぽいですね

Free Monad の出来上がり

sealed trait Free[F[+_], +A]
case class Pure[F[+_], +A](a: A) extends Free[F, A]
case class Suspend[F[+_], +A](f: F[Free[F, A]])
  extends Free[F, A]

Introduction of
Generic Programming
in Scala

@lyrical_logical

改め

Introduction of

Free Monad's
data structure

@lyrical_logical

でした

rpscala162

By りりろじ

rpscala162

  • 2,847