- 1. Making Logic Monad @halcat0x15a Recruit Marketing Partners Co., Ltd.
- 2. Table of Contents • Background:What is Logic Programming? • Backtracking and Uniﬁcation • Making Logic Monads • Practical Applications • Conclusions 今日話す内容です
- 3. Background Logic Monad is based on Logic Programming. First, we introduce Logic Programming with Prolog. Logicモナドは論理プログラミングに基づきます まず、Prologによる論理プログラミングを紹介します
- 4. Logic Programming Here is Prolog code: my_append concatenates two lists. これはPrologのコードです my_appendは2つのリストを連結します my_append([], L, L). my_append([H|T1], L, [H|T2]) :- my_append(T1, L, T2).
- 5. Logic Programming my_append takes three parameters. my_append has two clauses. my_appendは3つのパラメータをとります my_appendは2つの節をもちます
- 6. Logic Programming • The ﬁrst and second parameters are lists to be concatenated. • The third parameter is the concatenated result. 1つめと2つめのパラメータは連結されるリストです 3つめのパラメータは連結した結果です
- 7. Logic Programming Let's look at the ﬁrst clause. If the ﬁrst list is empty, the concatenated result equals the second list. 最初の節を見てみましょう 1つめのリストが空ならば連結した結果は2つめのリストです my_append([], L, L).
- 8. Logic Programming The head of the concatenated result equals the head of the ﬁrst list. The tail of the concatenated result is the result of concatenating the tail of the ﬁrst list with the second list. 連結した結果のheadは1つめのリストのheadと等しいです tailは1つめのリストのtailと2つめのリストを連結した結果です my_append([H|T1], L, [H|T2]) :- my_append(T1, L, T2).
- 9. Logic Programming The Prolog program run by giving a query. The queries can include logical variables. Prologのプログラムはクエリによって実行されます クエリは論理変数を含めることができます ?- my_append([1],[2,3],X). X = [1, 2, 3].
- 10. Logic Programming my_append can calculate the difference between two lists. my_appendはリストの差分を計算できます ?- my_append(X,[2,3],[1,2,3]). X = [1] ;
- 11. Logic Programming my_append can calculate combinations of two lists. my_appendは2つのリストの組み合わせを計算できます ?- my_append(X,Y,[1,2,3]). X = [], Y = [1, 2, 3] ; X = [1], Y = [2, 3] ; X = [1, 2], Y = [3] ; X = [1, 2, 3], Y = [] ;
- 12. Logic Programming my_append can perform various calculations with just this deﬁnition. my_appendはこの定義だけで様々な計算ができます my_append([], L, L). my_append([H|T1], L, [H|T2]) :- my_append(T1, L, T2).
- 13. Goal Today's goal is to realize my_append in Scala. Next, we introduce Backtracking and Uniﬁcation, the components of Logic Programming. 今日のゴールはmy_appendをScalaで実現することです 続いて論理プログラミングの構成要素について紹介します
- 14. Components • Backtracking • An algorithm for ﬁnding all solutions by trying combinations of variables and values. • Uniﬁcation • An algorithm for calculating the substitution that terms are identical. バックトラッキングは解の探索アルゴリズムです ユニフィケーションは置換を計算するアルゴリズムです
- 15. Backtracking Backtracking computations also exists in Scala Standard Library. Here is a backtracking computation: バックトラッキングはScalaの標準ライブラリにも存在します for { a <- List(1, 2, 3) if a % 2 == 0 } yield a
- 16. Backtracking Computations like ﬁlter can consider as backtracking. In other words, backtracking computations are MonadPlus. ﬁlterのような計算はバックトラッキングと見做せます つまりバックトラッキングはMonadPlusです
- 17. Backtracking MonadPlus satisfy the following interface: MonadPlusはこのようなインターフェースを満たします trait MonadPlus[M[_]] { def empty[A]: M[A] def plus[A](x: M[A], y: M[A]): M[A] def pure[A](a: A): M[A] def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B] }
- 18. Backtracking List is MonadPlus. ListはMonadPlusです trait ListMonadPlus extends MonadPlus[List] { def empty[A] = Nil def plus[A](x: List[A], y: List[A]) = x ::: y def pure[A](a: A) = List(a) def flatMap[A, B](ma: List[A])(f: A => List[B]) = ma.flatMap(f) }
- 19. Backtracking You can write backtracking computations using only MonadPlus primitives. MonadPlusの関数だけでバックトラッキングを書けます (List(1) ::: List(2) ::: List(3)).flatMap { a => if (a % 2 == 0) List(a) else Nil }
- 20. Backtracking The correspondence with primitives is as follows: プリミティブとの対応はこのようになります (pure(1) plus pure(2) plus pure(3)).flatMap { a => if (a % 2 == 0) pure(a) else empty }
- 21. Backtracking • plus represents a disjunction. • ﬂatMap represents a conjunction. • pure represents a success. • empty represents a failure. それぞれのプリミティブにはこのような対応があります
- 22. Uniﬁcation Uniﬁcation is like an assignment. Variables appear on the left and right sides. ユニフィケーションは代入のようなものです 変数は左辺と右辺にあらわれます A = 1 2 = B
- 23. Uniﬁcation Uniﬁcation is like a comparison. If a comparison fails, a program may backtrack. ユニフィケーションは比較のようなものです 比較に失敗した場合プログラムはバックトラックします 1 = 1 % success 2 = 1 % failure
- 24. Uniﬁcation Data structures like List and Map unify elements recursively. ユニフィケーションは再帰的に行われます [A] = [1] {x: 1, y: B} = {x: C, y: 2}
- 25. Uniﬁcation Uniﬁcation can be implemented by creating logical variable relations. ユニフィケーションは変数の関係を作ることで実装できます
- 26. Uniﬁcation The logical variable can be deﬁned as follows: 論理変数はこのように定義できます sealed trait LVar[A] case class Bound[A](value: A) extends LVar[A] case class Unbound[A](id: Int) extends LVar[A]
- 27. Uniﬁcation The result of the above uniﬁcation is as follows: 上記のユニフィケーションの結果はこのようになります Map( 0 -> Bound("foo"), 1 -> Bound("bar") ) A = "foo" "bar" = B
- 28. Uniﬁcation The result of the above uniﬁcation is as follows: 上記のユニフィケーションの結果はこのようになります C = D Map( 0 -> Bound("foo"), 1 -> Bound("bar"), 2 -> Unbound(3), 3 -> Unbound(2) )
- 29. Uniﬁcation The result of the above uniﬁcation is as follows: 上記のユニフィケーションの結果はこのようになります Map( 0 -> Bound("foo"), 1 -> Bound("bar"), 2 -> Unbound(3), 3 -> Bound("baz") ) D = "baz"
- 30. Uniﬁcation The implementation of Uniﬁcation requires to express the state. Backtracking computations and mutable data types are incompatible. Thus we introduce the State monad. バックトラッキングと可変データ型は相性が悪いです そこでStateモナドを導入します
- 31. Making Logic Monads Logic is a combination of List monad and State monad. LogicはListモナドとStateモナドの組み合わせです case class Logic[A](apply: State => List[(State, A)]) { def flatMap[B](f: A => Logic[B]): Logic[B] = Logic(s => apply(s).flatMap { case (s, a) => f(a).apply(s) }) def run: List[A] = apply(State.initial).map(_._2) }
- 32. Making Logic Monads State has a logical variable identiﬁer and relations. Stateは論理変数のidと対応をもちます case class State(id: Int, vars: IntMap[LVar[Any]]) object State { val initial = State(0, IntMap.empty) }
- 33. Making Logic Monads Logic has the following primitives: Logicにはこのようなプリミティブがあります def success[A](a: A): Logic[A] def failure[A]: Logic[A] def choose[A](x: Logic[A], y: Logic[A]): Logic[A] def get: Logic[State] def put(state: State): Logic[Unit]
- 34. Making Logic Monads Logic is MonadPlus. LogicはMonadPlusです trait LogicMonadPlus extends MonadPlus[Logic] { def empty[A] = failure def plus[A](x: Logic[A], y: Logic[A]) = choose(x, y) def pure[A](a: A) = success(a) def flatMap[A, B](ma: Logic[A])(f: A => Logic[B]) = ma.flatMap(f) }
- 35. Making Logic Monads The following aliases are useful as logical operators: このような別名は論理演算子として便利です def &&&[B](that: => Logic[B]): Logic[B] = flatMap(_ => that) def |||(that: => Logic[A]): Logic[A] = choose(this, that)
- 36. Making Logic Monads Logic is the State monad. fresh creates a unique logical variable. LogicはStateモナドです freshは一意な論理変数を作ります def fresh[A]: Logic[LVar[A]] = for { state <- get _ <- put(state.copy(id = state.id + 1)) } yield Unbound(state.id)
- 37. Uniﬁcation Unify is a type class for uniﬁcation. The uniﬁcation result type is Logic. Unifyはユニフィケーションのための型クラスです trait Unify[A] { def unify(x: A, y: A): Logic[Unit] }
- 38. Uniﬁcation The universal uniﬁcation is as follows: 普遍的なユニフィケーションはこのようになります implicit def UniversalUnify[A]: Unify[A] = new Unify[A] { def unify(x: A, y: A) = if (x == y) success(()) else failure }
- 39. Uniﬁcation Uniﬁcation operators are provided as follows: ユニフィケーションの演算子はこのように提供されます implicit class UnifyOps[A](val self: A) { def ===(that: A)(implicit A: Unify[A]) = A.unify(self, that) }
- 40. Uniﬁcation An example of a simple uniﬁcation using Logic: Logicを使ったシンプルなUniﬁcationの例です scala> val e0 = for { | a <- LVar.fresh[String] | _ <- a === "foo" ||| a === "bar" | a <- a.get | } yield a scala> println(e0.run) List(foo, bar)
- 41. Uniﬁcation get gets the value from a logical variable. getは論理変数から値を取得します def get: Logic[A] = this match { case Bound(a) => Logic.success(a) case Unbound(id) => for { state <- Logic.get a <- find(id, state.vars).fold(Logic.failure[A]) { a => Logic.success(a.asInstanceOf[A]) } } yield a }
- 42. Uniﬁcation ﬁnd follows relationships recursively. ﬁndは関係を再帰的に辿ります def find(key: Int, vars: Map[Int, LVar[Any]]) = { @tailrec def loop(key: Int, path: Vector[Int]) = vars.get(key) match { case None => None case Some(Bound(v)) => Some(v) case Some(Unbound(k)) if path.contains(k) => None case Some(Unbound(k)) => loop(k, path :+ k) } loop(key, Vector(key)) }
- 43. my_append in Scala Now we are almost ready to implement my_append. But, Prolog’s pattern matching is difﬁcult to realize in Scala. Thus we translate pattern matching into primitive operations. PrologのパターンマッチをScalaで実現するのは難しいです そこで、パターンマッチを原始的な操作に翻訳します
- 44. my_append in Scala Pattern matching is expressed as conjunctions (,), disjunctions (;), and uniﬁcation. パターンマッチは論理積、論理和、ユニフィケーションとして 表現されます my_append(X, Y, Z) :- X = [], Y = Z; X = [H|T1], Z = [H|T2], my_append(T1, Y, T2).
- 45. my_append in Scala my_append in Scala is as follows: Scalaのmy_appendはこのようになります def append[A: Unify](x: LVar[LList[A]], y: LVar[LList[A]], z: LVar[LList[A]]): Logic[Unit] = for { h <- LVar.fresh[A] t1 <- LVar.fresh[LList[A]] t2 <- LVar.fresh[LList[A]] _ <- x === LNil[A] &&& y === z ||| x === LCons(h, t1) &&& z === LCons(h, t2) &&& append(xt, y, zt) } yield ()
- 46. my_append in Scala LList is List using LVar. LListはLVarを使ったListです sealed trait LList[A] case class LNil[A]() extends LList[A] case class LCons[A]( head: LVar[A], tail: LVar[LList[A]] ) extends LList[A]
- 47. my_append in Scala The uniﬁcation of LList uniﬁes a head and a tail respectively. LListはheadとtailをそれぞれユニファイします implicit def LListUnify[A](implicit A: Unify[A]) = new Unify[LList[A]] { def unify(x: LList[A], y: LList[A]) = (x, y) match { case (LNil(), LNil()) => success(()) case (LCons(x, xs), LCons(y, ys)) => x === y &&& xs === ys case _ => failure } }
- 48. my_append in Scala LList can be converted to Scala's List. LListはScalaのリストに変換することができます def toList: Logic[List[A]] = this match { case LNil() => success(Nil) case LCons(h, t) => for { h <- h.get t <- t.get t <- t.toList } yield h :: t }
- 49. my_append in Scala my_append works as follows: my_appendはこのように動作します scala>val e1 = for { | x <- LVar.fresh[LList[Int]] | _ <- append(LList(1, 2), LList(3), x) | x <- x.toList |} yield x scala>println(e1.run) List(List(1, 2, 3))
- 50. my_append in Scala my_append works as follows: my_appendはこのように動作します scala>val e2 = for { | x <- LVar.fresh[LList[Int]] | y <- LVar.fresh[LList[Int]] | _ <- append(x, y, LList(1, 2, 3)) | x <- x.toList; y <- y.toList |} yield (x, y) scala>println(e2.run) List((List(),List(1, 2, 3)), (List(1),List(2, 3)), (List(1, 2),List(3)), (List(1, 2, 3),List()))
- 51. my_append in Scala Congrats! It seems to be working properly. 正しく動いているようにみえます
- 52. Problems • This implementation is not stack-safe. • This implementation depends on speciﬁc data structures like List. 問題として、この実装はスタックセーフではなく Listのような特定のデータ構造に依存します
- 53. Problems We introduce Freer monads to solve these problems. これらの問題を解決するためにFreerモナドを導入します
- 54. Freer Monads Freer monads can represent arbitrary monads. Modern implementations of Freer monads use type-aligned sequences. Freerモナドは任意のモナドを表現できます モダンな実装にはType-aligned Sequenceが使われます
- 55. Type-aligned Sequence Type-aligned Sequence is a sequence of functions that represents a function. Function composition is implemented by concatenating sequences. Type-aligned Sequenceは関数を表現する関数の列です 関数の合成は列の結合で実装されます val f: A => C = TASeq(A => B, B => C) val g: A => D = f ++ TASeq(C => D)
- 56. Type-aligned Sequence Function application is performed in stack-safe by applying from the head of the sequence. 関数の適用はスタックセーフに実行されます val h = TASeq(A => B, B => C, C => D) val D = h(A)
- 57. Freer Monads Freer monad has two constructors: Freerモナドは2つのコンストラクタをもちます sealed trait Freer[F[_], A] case class Pure[F[_], A](value: A) extends Freer[F, A] case class Impure[F[_], A, B]( effect: F[A], arrs: Arrs[F, A, B] ) extends Freer[F, B]
- 58. Freer Monads • Pure represents a computation without side effects. • Impure represents a computation with side effects. Pureは副作用のない計算を表します Impureは副作用付きの計算を表します
- 59. Freer Monads Arrs is Type-aligned Sequence specialized for Freer. ArrsはFreerに特殊化されたType-aligned Sequenceです trait Arrs[F[_], A, B] { def :+[C](f: B => Freer[F, C]): Arrs[F, A, C] def ++[C](that: Arrs[F, B, C]): Arrs[F, A, C] def apply(a: A): Freer[F, B] }
- 60. Freer Monads Freer Monads using Type-aligned Sequence achieves fast monad composition. TASeq使ったFreerは高速なモナドの合成を実現します def flatMap[B](f: A => Freer[F, B]): Freer[F, B] = this match { case Pure(a) => f(a) case Impure(fa, k) => Impure(fa, k :+ f) }
- 61. Freer Monads Side effects mean the following computations: 副作用とはこのような計算を意味します def failure[A]: Logic[A] def choose[A](x: Logic[A], y: Logic[A]): Logic[A] def get: Logic[State] def put(state: State): Logic[Unit]
- 62. Algebraic Effects Logic effects can be deﬁned as data types. Logicの副作用をデータ型として定義できます sealed trait LogicEffect[+A] case object Failure extends LogicEffect[Nothing] case object Choose extends LogicEffect[Boolean] case object Get extends LogicEffect[State] case class Put(state: State) extends LogicEffect[Unit]
- 63. Algebraic Effects Logic is deﬁned as an alias for Freer. LogicはFreerの別名として定義されます type Logic[A] = Freer[LogicEffect, A] def success[A](a: A): Logic[A] = Pure(a) def failure[A]: Logic[A] = Impure(Failure, Arrs.id)
- 64. Algebraic Effects choose switches continuations with Boolean. chooseはBooleanによって継続を切り替えます def choose[A](x: Logic[A], y: Logic[A]): Logic[A] = Impure(Choose, Arrs.id).flatMap(if (_) x else y) def get: Logic[State] = Impure(Get, Arrs.id) def put(state: State): Logic[Unit] = Impure(Put(state), Arrs.id)
- 65. Effect Handlers Logic can be handled as List. LogicはListとして処理できます def runList[A](logic: Logic[A], s: State): List[A] = logic match { case Pure(a) => List(a) case Impure(Failure, _) => Nil case Impure(Choose, k) => runList(s)(k(true)) ::: runList(s)(k(false)) case Impure(Get, k) => runList(s)(k(s)) case Impure(Put(s), k) => runList(s)(k(())) }
- 66. Effect Handlers We can deﬁne various other effect handlers. • Handle in constant space asVector • Handle inﬁnite solutions as Stream • Customize solution search algorithm 他にも様々なエフェクトハンドラを定義することができます
- 67. Practical Applications Let's consider a practical application with Logic. It is good to have loops and conditionals. ここでLogicによる実践的なアプリケーションを考えます ループや条件分岐を含むものが良いです
- 68. Practical Applications So it's Fizz Buzz. そこでFizz Buzzです def fizzbuzz(n: Int): Logic[String] = (n % 15 === 0).map(_ => "FizzBuzz") ||| (n % 3 === 0).map(_ => "Fizz") ||| (n % 5 === 0).map(_ => "Buzz") ||| success(n.toString)
- 69. Practical Applications But there is a problem with this Fizz Buzz: The problem is that backtracking gives you all solutions. しかしこのFizzBuzzには問題があって、 バックトラッキングによって全ての解が得られてしまいます scala> println(run(fizzbuzz(15))) List(FizzBuzz, Fizz, Buzz, 15)
- 70. Practical Applications Prolog solves the problem by using the cut operator. The cut operator can perform pruning and logical negation. Prologはcut演算子によりこの問題を解決しています cutは枝刈りや論理的な否定ができます
- 71. Practical Applications There is one primitive to implement the functionality of the cut operator: cutの機能を実装するためのプリミティブがあります def split[A](logic: Logic[A]) : Logic[Option[(A, Logic[A])]]
- 72. Practical Applications split is an operation that splits Logic into head and tail. If it can be split, it returns Some, otherwise it returns None. splitはLogicをheadとtailに分割します 分割できればSomeで返し、そうでないならNoneを返します
- 73. Practical Applications Freer monads are good at partially handling side effects. The next slide shows the implementation. Freerモナドは副作用を部分的に処理することが得意です 次のスライドに実装を示します
- 74. Practical Applications splitの実装です def split[A](logic: Logic[A]) = { def go(logic: Logic[A], state: State) = logic match { case Pure(a) => success(Some((a, failure))) case Impure(Failure, _) => success(None) case Impure(Choose, k) => go(k(true), state).map(_.map { case (a, t) => (a, t ||| k(false)) }) case Impure(Get, k) => go(k(state), state) case Impure(Put(state), k) => go(k(()), state) } get.flatMap(go(logic, _)) }
- 75. Practical Applications We can deﬁne a conditional with split: splitで条件分岐を定義できます def ifte[A, B](logic: Logic[A])(th: A => Logic[B])(el: Logic[B]): Logic[B] = split(logic).flatMap { case None => el case Some((a, t)) => th(a) ||| t.flatMap(th) }
- 76. Practical Applications Make useful operators. 便利な演算子を作って def orElse(that: => Logic[A]): Logic[A] = ifte(this)(success)(that)
- 77. Practical Applications Rewrite Fizz Buzz. それで書き換えて def fizzbuzz(n: Int): Logic[String] = (n % 15 === 0).map(_ => "FizzBuzz") orElse (n % 3 === 0).map(_ => "Fizz") orElse (n % 5 === 0).map(_ => "Buzz") orElse success(n.toString)
- 78. Practical Applications Perfect! 完璧です scala>val e3 = for { | n <- (1 to 15).foldMap(success) | s <- fizzbuzz(n) |} yield s scala> println(run(e3)) List(1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz)
- 79. Conclusions • Logic Programming consists of Backtracking and Uniﬁcation. • Backtracking computations is MonadPlus. • Freer monads and Type-aligned Sequence provide stack safety. • The representation of effects as data types can give different interpretations. 結論です
- 80. References • https://github.com/halcat0x15a/logical • http://okmij.org/ftp/Computation/LogicT.pdf • http://okmij.org/ftp/Haskell/extensible/ more.pdf 参考文献です
- 81. Thank you