$30 off During Our Annual Pro Sale. View Details »

dali introduction

dali introduction

slide for my talk in rpscala#252 https://rpscala.connpass.com/event/147143/

TSUYUSATO Kitsune

September 17, 2019
Tweet

More Decks by TSUYUSATO Kitsune

Other Decks in Programming

Transcript

  1. 例: Expr 数式を表す trait とその実装たち。 (現在は⾜し算しかない) sealed trait Expr[A] case

    class Value[A](a: A) extends Expr[A] case class Add[A](l: Expr[A], r: Expr[A]) extends Expr[A] 6
  2. 例: Expr の⽐較 (⼀般Lv. 0: 愚直) 値の部分を String から Int

    にする。 def toInt(e: Expr[String]): Expr[Int] = e match { case Value(a) => Value(a.toInt) case Add(l, r) => Add(toInt(l), toInt(r)) } toInt(Add(Value("10"), Value("20"))) // => Add(Value(10), Value(20)) 7
  3. 例: Expr の変換 (⼀般Lv. 0: 愚直) 値の部分を Int から String

    にする。 def toString(e: Expr[Int]): Expr[String] = e match { case Value(a) => Value(a.toString) case Add(l, r) => Add(toString(l), toString(r)) } toString(Add(Value(10), Value(20))) // => Add(Value("10"), Value("20")) ほとんど同じ。 8
  4. 例: Expr の変換 (⼀般Lv. 1: map ) map 関数を作る。 def

    map[A, B](e: Expr[A])(f: A => B): Expr[B] = e match { case Value(a) => Value(f(a)) case Add(l, r) => Add(map(l)(f), map(r)(f)) } def toInt(e: Expr[String]): Expr[Int] = map(e, _.toInt) def toString(e: Expr[Int]): Expr[String] = map(e, _.toString) 10
  5. 例: Expr の変換 (⼀般Lv. 1: map ) いい感じ。 toInt(Add(Value("10"), Value("20")))

    // => Add(Value(10), Value(20)) toString(Add(Value(10), Value(20))) // => Add(Value("10"), Value("20")) 11
  6. 例: Expr の変換 (⼀般Lv. 1: map ) 引き算を表すクラス Sub が増えた。

    case class Sub[A](l: Expr[A], r: Expr[A]) extends Expr[A] どうする? 12
  7. 例: Expr の変換 (⼀般Lv. 1: map ) map を修正する。 def

    map[A, B](e: Expr[A])(f: A => B): Expr[B] = e match { case Value(a) => Value(f(a)) case Add(l, r) => Add(map(l)(f), map(r)(f)) // 追加: case Sub(l, r) => Sub(map(l)(f), map(r)(f)) } 13
  8. 例: Expr の変換 (拡張) 掛け算を表す Mul と割り算を表す Div も増えた。 case

    class Mul[A](l: Expr[A], r: Expr[A]) extends Expr[A] case class Div[A](l: Expr[A], r: Expr[A]) extends Expr[A] また map を修正すれば良さそうだけど‥‥ 。 14
  9. 例: Expr のその他の操作 (少し話題を変えて) Expr の他の操作も考えられる。 等価性の⽐較 ⽂字列化 数式として評価 Add(Value(10),

    Value(20)) -> 30 これらは map では⼀般化できなさそうなので、個別に実装する必要がありそう。 Mul とか Div が増えたら、これも修正するの‥‥ ? 15
  10. 例: Expr の変換 (⼀般Lv. 1: map ) ところで、 map の

    Add と Sub を処理する部分: case Add(l, r) => Add(map(l)(f), map(r)(f)) case Sub(l, r) => Sub(map(l)(f), map(r)(f)) ほとんど同じ。 17
  11. つまり何が⾔いたいのか どんな構造でも表現できるすごいデータ構造があったとする。 元のデータ構造( Expr )からその構造 への変換と、 その構造 に対する操作( map ,

    equal , show など)があれば、 型を拡張した場合に、その構造 への変換を拡張するだけで、 他の操作も拡張できる。 そんな都合のいいデータ構造があるのか? 23
  12. HList いくつでも要素を格納できる Tuple あるいは、 head と tail で異なる型を格納する List sealed

    trait HList { def :*:[H](head: H): H :*: this.type = new :*:(head, this) } sealed class HNil extends HList object HNil extends HNil case class :*:[H, T <: HList](head: H, tail: T) extends HList 25
  13. HList 例 例: 型 Int :*: String :*: Boolean :*:

    HNil 1要素⽬が Int 、2要素⽬が String 、3要素⽬が Boolean の HList 例: 値 1 :*: "foo" :*: true :*: HNil どうやって値を取り出すの? →パターンマッチとかプロパティとか、普通の⽅法で取り出せる。 def x(h: Int :*: String :*: Boolean :*: HNil): String = h match { case _ :*: s :*: _ :*: HNil => s } x(1 :*: "foo" :*: true :*: HNil) // => "foo" 26
  14. Coproduct sealed trait Coproduct sealed trait CNil extends Coproduct sealed

    trait :+:[H, T <: Coproduct] extends Coproduct case class Inl[H, T <: Coproduct](head: H) extends :+:[H, T] case class Inr[H, T <: Coproduct](tail: T) extends :+:[H, T] 27
  15. Coproduct 例 例: 型 Int :+: String :+: Boolean :+:

    CNil Int 、 String 、 Boolean のうち、どれかの値を取る型 例: 値 Inl[Int, String :+: Boolean :+: CNil](1) 上の型で、 Int を持つ Coproduct の値 HList と同じく、パターンマッチなどで値を取り出す。 28
  16. Expr[A] Expr[A] に対応する型は Value[A] :+: Add[A] :+: Sub[A] :+: CNil

    。 ( Mul と Div はまだ追加してないことにする) implicit def exprConverter[A] = new Converter[Expr[A], Value[A] :+: Add[A] :+: Sub[A] :+: CNil] { def from[A](e: Expr[A]): Value[A] :+: Add[A] :+: Sub[A] :+: CNil = e match { case v: Value[A] => Inl(v) case a: Add[A] => Inr(Inl(a)) case s: Sub[A] => Inr(Inr(Inl(s))) } def to[A](e: Value[A] :+: Add[A] :+: Sub[A] :+: CNil): Expr[A] = ??? // 略 } 31
  17. Value[A] Value[A] に対応する型は A :*: HNil 。 ( case class

    Value[A](a: A) なので) implicit def valueConverter[A] = new Converter[Value[A], A :*: HNil] { def from[A](v: Value[A]): A :*: HNil = v.a :*: HNil def to[A](v: A :*: HNil): Value[A] = Value(v.head) } 32
  18. Add[A] Add[A] に対応する型は Expr[A] :*: Expr[A] :*: HNil 。 (

    case class Add[A](l: Expr[A], r: Expr[A]) なので) implicit def addConverter[A] = new Converter[Add[A], Expr[A] :*: Expr[A] :*: HNil] { def from[A](a: Add[A]): Expr[A] :*: Expr[A] :*: HNil = a.l :*: a.r :*: HNil def to[A](a: Expr[A] :*: Expr[A] :*: HNil): Add[A] = Value(a.head, a.tail.head) } Sub[A] も同じく。 33
  19. :*: に対する実装 head と tail 両⽅が等しい場合に等しい。 implicit def hconsEqual[H, T

    <: HList]( implicit heq: Equal[H], teq: Equal[T]) = new Equal[H :*: T] { def equal(x: H :*: T, y: H :*: T): Boolean = heq.equal(x.head, y.head) && teq.equal(x.tail, y.tail) } 36
  20. :+: に対する実装 持ってる値の位置が同じで、等しければ等しい。 implicit def cconsEqual[H, T <: Coproduct]( implicit

    heq: Equal[H], teq: Equal[T]) = new Equal[H :+: T] { def equal(x: H :+: T, y: H :+: T): Boolean = (x, y) match { case (Inl(xh), Inl(yh)) => heq.equal(xh, yh) case (Inr(xt), Inr(yt)) => teq.equal(xt, yt) case _ => false } } 38
  21. 基本的な型に対する Equal の実装 implicit val intEqual: Equal[Int] = (x, y)

    => x == y implicit val stringEqual: Equal[String] = (x, y) => x == y 39
  22. Converter を使った Equal の実装 implicit def converterEqual[A, B]( implicit AB:

    Converter[A, B], beq: Equal[B]) = new Equal[A] { def equal(x: A, y: A) = beq.equal(AB.from(x), AB.from(y)) } 40
  23. Mul , Div を追加した場合でも、 Mul , Div に対応する Converter を実装するだけで

    Equal のインスタンスが導出できる。 また、全く関係ない型でも Converter を⽤意できれば Equal のインスタンスが得られるよ うになる。 sealed class MyOption[A] case class MyNone[A]() extends MyOption[A] case class MySome[A](a: A) extends MyOption[A] // ...MyOption へのConverter の実装... implicitly[Equal[MyOption[Int]]].equal(MySome(1), MySome(2)) とてもすごい。 42
  24. ここまでのまとめ HList , Coproduct との変換と、 HList , Coproduct での処理を ⽤意すると、元の型でもその処理が使えるようになる。

    変換と処理は疎なので、⼀度 HList , Coproduct で定義した処理は、 変換のある様々な型で使うことができる。 43
  25. Generic Programming Libiary shapeless, dali (拙作) HList , Coproduct などのデータ構造

    それらの型とデータ構造の変換を⾃動的に作るマクロ 46
  26. dali.higher.Generic1 Generic1.Aux[F[_], R <: TypeFunction1] F[_] と R (に具体的な型を指定したもの)との変換を提供。 TypeFunction1

    の具体的な型には HList1 や Coproduct1 などがある。 (個⼈的には)分かりやすいと思う。 51
  27. そもそもdaliとは? MakeNowJust/dali Scala 2.13の機能を前提に作ったGeneric Programming Library 最低限の機能のみを提供(minimalism) catsの⽅クラスの⾃動導出も別ライブラリとして提供 Functor などだけではなく、

    Alternative や Monad などの導出にも対応 まだMaven Central Repositoryにはリリースしていない‥‥ (今週中には) Scala 2.13が使えるなら結構良いライブラリだと思う(⾃画⾃賛) 52