2. Table of Contents
• Background:What is Logic Programming?
• Backtracking and Unification
• 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).
6. Logic Programming
• The first 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 first clause.
If the first 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 first list.
The tail of the concatenated result is the result
of concatenating the tail of the first 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 definition.
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
Unification, the components of Logic
Programming.
今日のゴールはmy_appendをScalaで実現することです
続いて論理プログラミングの構成要素について紹介します
14. Components
• Backtracking
• An algorithm for finding all solutions by
trying combinations of variables and
values.
• Unification
• 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 filter can consider as
backtracking.
In other words, backtracking computations are
MonadPlus.
filterのような計算はバックトラッキングと見做せます
つまりバックトラッキングはMonadPlusです
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.
• flatMap represents a conjunction.
• pure represents a success.
• empty represents a failure.
それぞれのプリミティブにはこのような対応があります
22. Unification
Unification is like an assignment.
Variables appear on the left and right sides.
ユニフィケーションは代入のようなものです
変数は左辺と右辺にあらわれます
A = 1
2 = B
23. Unification
Unification is like a comparison.
If a comparison fails, a program may backtrack.
ユニフィケーションは比較のようなものです
比較に失敗した場合プログラムはバックトラックします
1 = 1 % success
2 = 1 % failure
24. Unification
Data structures like List and Map unify
elements recursively.
ユニフィケーションは再帰的に行われます
[A] = [1]
{x: 1, y: B} = {x: C, y: 2}
26. Unification
The logical variable can be defined 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. Unification
The result of the above unification is as follows:
上記のユニフィケーションの結果はこのようになります
Map(
0 -> Bound("foo"),
1 -> Bound("bar")
)
A = "foo"
"bar" = B
28. Unification
The result of the above unification is as follows:
上記のユニフィケーションの結果はこのようになります
C = D
Map(
0 -> Bound("foo"),
1 -> Bound("bar"),
2 -> Unbound(3),
3 -> Unbound(2)
)
29. Unification
The result of the above unification is as follows:
上記のユニフィケーションの結果はこのようになります
Map(
0 -> Bound("foo"),
1 -> Bound("bar"),
2 -> Unbound(3),
3 -> Bound("baz")
)
D = "baz"
30. Unification
The implementation of Unification 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 identifier 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. Unification
Unify is a type class for unification.
The unification result type is Logic.
Unifyはユニフィケーションのための型クラスです
trait Unify[A] {
def unify(x: A, y: A): Logic[Unit]
}
38. Unification
The universal unification 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. Unification
Unification operators are provided as follows:
ユニフィケーションの演算子はこのように提供されます
implicit class UnifyOps[A](val self: A) {
def ===(that: A)(implicit A: Unify[A]) =
A.unify(self, that)
}
40. Unification
An example of a simple unification using Logic:
Logicを使ったシンプルなUnificationの例です
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. Unification
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. Unification
find follows relationships recursively.
findは関係を再帰的に辿ります
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 difficult 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 unification.
パターンマッチは論理積、論理和、ユニフィケーションとして
表現されます
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 unification of LList unifies 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()))
52. Problems
• This implementation is not stack-safe.
• This implementation depends on specific
data structures like List.
問題として、この実装はスタックセーフではなく
Listのような特定のデータ構造に依存します
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 defined 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 defined 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)
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 define various other effect handlers.
• Handle in constant space asVector
• Handle infinite 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による実践的なアプリケーションを考えます
ループや条件分岐を含むものが良いです
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 define 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)
}
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 Unification.
• Backtracking computations is MonadPlus.
• Freer monads and Type-aligned Sequence
provide stack safety.
• The representation of effects as data types
can give different interpretations.
結論です