Rewriting Engine for Process Algebras
Plan
• Introduction to Process Algebras•Approaches to Process Algebra Engines•FreeACP: the rewriting engine
Introduction to Process Algebras
Process Algebras•Communicating sequential processes (CSP) – Tony Hoare, 1977•Calculus of communicating systems (CCS) – Robin Miller, 1980•Algebra of Communicating Processes (ACP) – Jan Bergstra and Jan Willem Klop, 1982
ACP Axioms
Example: GUI - Traditional approach
val firstButtonCallback: Event => Unit = e => { if (e.button == first) { eventSystem.deregister(firstButtonCallback ) eventSystem.deregister(secondButtonCallback)
atGuiThread { textField.text = "Hello World" } }}
Example: GUI - Traditional approachval secondButtonCallback: Event => Unit = e => { if (e.button == second) { eventSystem.deregister(firstButtonCallback ) eventSystem.deregister(secondButtonCallback)
atGuiThread { textField.text = "Something Else" } }}
eventSystem.register(firstButtonCallback )eventSystem.register(secondButtonCallback)
Example: GUI – ACP approach
button(first ) * setText(textField, "Hello World" ) +button(second) * setText(textField, "Something Else")
Approaches to Process Algebra Engines
SubScript: Actor-based implementation
SubScript: Actor-based implementation
FreeACP: Rewriting implementation1. ax + by // Initial expression2. εx + bx // a evaluated to success - ε3. εx // ε resolves the choice `+`4. x // x after empty action is the
// same as just x
Rewriting Engine rules• Some PA axioms can be reflected in the engine via symbolic rewriting
of expressions, e.g.: εx = x; δx = δ.• Some PA expressions can’t be further reduced without performing a
computation, e.g.: ax, where a is an atomic action. The rules that need to perform a computation before execution are suspension rules.• To evaluate ax, one first need to evaluate a. a evaluates to either ε
or δ, and the expression is transformed to either εx or δx which can be reduced via symbolic rewriting.
Rewriting Engine rules
FreeACP Implementation
Tree: a representation of a PA expression
trait Tree[S[_]]
case class Suspend [S[_]](a : S[Tree[S]]) extends Tree[S]case class Call [S[_]](t : () => Tree[S] ) extends Tree[S]case class Sequence[S[_]](ts: List[Tree[S]]) extends Tree[S]case class Choice [S[_]](ts: List[Tree[S]]) extends Tree[S]
trait Result[S[_]] extends Tree[S]case class Success[S[_]]() extends Result[S]case class Failure[S[_]]() extends Result[S]
case class Loop[S[_]]() extends Tree[S]
Symbolic rewriting rulesdef rewrite: PartialFunction[Tree[S], Tree[S]] = _ match { // Sequence case Sequence(Nil ) => Success() case Sequence(Sequence(a) :: x) => Sequence(a ++ x) case Sequence(Call(t) :: x) => Sequence(t() :: x) case Sequence(Success() :: x) => Sequence(x) case Sequence(Failure() :: x) => Failure() // To be continued…
Symbolic rewriting rules // Choice case Choice(Nil) => Failure() case Choice (Choice(x) :: y) => Choice(x ++ y) case Sequence(Choice(x) :: y) => Choice(x.map { t => Sequence(t :: y) }) case Choice(x) if x.contains(Success()) => Success() case Choice(x) if x.contains(Failure()) => Choice(x.filter(_ != Failure())) case Choice(x) if x.exists(!resume.isDefinedAt(_)) => Choice(x.map { case a if !resume.isDefinedAt(a) => rewrite.apply(a) case a => a })}
Suspension rulesimplicit val F: Functor[S]
def resume: PartialFunction[ Tree[S], List[S[Tree[S]]] ] = _ match { // Atom case Suspend(r: S[Tree[S]]) => List(r) // Sequence case Sequence(Suspend(r: S[Tree[S]]) :: x) => List( F.map(r) { rs => Sequence(rs :: x) } )
// Choice case Choice(x @ _ :: _) if x.forall(resume.isDefinedAt) => x.flatMap(resume)}
Executiondef run(debug: Boolean = false)(implicit C: Comonad[S], SG: MonoidK[S]): Result[S] = runM(new (S ~> S) { override def apply[A](x: S[A]): S[A] = x }, debug)
def runM[G[_]: Suspended, Functor](f: S ~> G) (implicit G: Comonad[G], M: MonoidK[G]): Result[G] = { @annotation.tailrec def loop(t: Tree[S]): Result[G] = t match { case _: Success[S] => Success[G]() case _: Failure[S] => Failure[G]() case t => loop ({ if (!resume.isDefinedAt(t)) rewrite.apply(t) else Comonad[G].extract { Foldable[List].combineAll( resume.apply(t).map(f.apply[Tree[S]]) )(M.algebra[Tree[S]]) } }) } loop(this, steps) }
LanguageT: a free S[_]trait LanguageT[+T]case class Atom(a: () => Unit) extends LanguageT[Result[LanguageT]]case class MapLanguage[A, B](t1: LanguageT[A], f : A => B) extends LanguageT[B]case class SuspendedLanguage[A](x: () => A) extends LanguageT[A]
object LanguageT { type Language = Tree[LanguageT]
def atom(a: => Unit): Language = Suspend[LanguageT](Atom( () => a )) def call(t: => Language): Language = Call[LanguageT] { () => t }
def ε = Success[LanguageT]() def δ = Failure[LanguageT]()}
Compiler for LanguageTdef defaultCompiler[F[_]](implicit F: Functor[F], S: Suspended[F]): PartialCompiler[F] = mainCompiler => new (LanguageT ~> OptionK[F, ?]) { override def apply[A](x: LanguageT[A]): Option[F[A]] = ({ case Atom(s) => S.suspend { try { s(); Success[LanguageT]().asInstanceOf[A] } // TODO: Casts catch { case t: Throwable => Failure[LanguageT]().asInstanceOf[A] } }
case MapLanguage(t1, f) => F.map(mainCompiler(t1))(f) case SuspendedLanguage(x) => S(x) }: PartialFunction[LanguageT[A], F[A]]).lift.apply(x) }
Current state of FreeACP• Supports sequence “*”, choice “+”• Extensibility due to the free S[_] (GUI example available for Scala
Swing)• Inherent Limitations
• Centralization of the coordination logic• Expressions’ size influences runtime complexity: certain rules require the engine
to traverse the entire list of operator’s children each time rewriting is done.
• Inspired by the implementation of the Free Monad• https://github.com/anatoliykmetyuk/free-acp• http://subscript-lang.org/
Q&A