Skip to content

Instantly share code, notes, and snippets.

@tgrospic
Last active July 21, 2020 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tgrospic/8ae9ac09d900d314f81753d30d71c411 to your computer and use it in GitHub Desktop.
Save tgrospic/8ae9ac09d900d314f81753d30d71c411 to your computer and use it in GitHub Desktop.
Simple Rholang with higher-order channels
object rholang {
/**
* This interface represents terms in our Simple Rholang language.
* Everything is a process.
*/
trait Process
/**
* AST of our Simple Rholang language. It's private and accessible only
* inside this object. The only way to change the state (Set[Process]) is to
* call defined operations (nil, par, send, receive).
*/
private final case class Par(s: Set[Process] = Set()) extends Process {
override def toString: String = s"${s.mkString(" | ")}"
}
private final case class Send(name: Process) extends Process
private final case class Receive(name: Process) extends Process
/**
* `nil` and `par` are Monoid operations (empty, combine).
*
* All terms (processes) are collected in a Set defined in Par(Set[Process]).
*/
def nil: Process = Par(s = Set())
def par(p1: Process, p2: Process = nil): Process = (p1, p2) match {
// Combine states from both Par's
// This rule also "erase" `nil` from both sides of Par
// nil | p == p == p | nil
case (Par(s1), Par(s2)) => Par(s1 ++ s2)
// Next two cases flatten nested Par's
// Par(Set(Par(Send(A)))) => Par(Set(Send(A)))
case (Par(s1), p2) => Par(s1 + p2)
case (p1, Par(s2)) => Par(s2 + p1)
// All other cases just collect in a Set
case (p1, p2) => Par(Set(p1, p2))
}
/**
* `send` and `receive` represent basic operations.
*/
def send(name: Process): Process = par(Send(name))
def receive(name: Process): Process = par(Receive(name))
/**
* Support for ground types (built-in processes).
*/
private trait Ground extends Process
private final case class GString(x: String) extends Ground {
override def toString: String = s""""${x}""""
}
private final case class GInt(x: Int) extends Ground {
override def toString: String = s"${x}"
}
private final case class GSystem(id: String, proc: Process) extends Ground {
override def toString: String = s"SYS($id, $proc})"
}
private final case class GTuple(args: Seq[Process]) extends Ground {
override def toString: String = s"(${args.mkString(", ")})"
}
def string(x: String): Process = par(GString(x))
def int(x: Int): Process = par(GInt(x))
def sys(x: String, proc: Process): Process = par(GSystem(x, proc))
def tuple(p1: Process, p2: Process): Process = par(GTuple(Seq(p1, p2)))
/**
* Converter to process
*/
trait ToProcess[A] {
def convertToProcess(a: A): Process
}
// Helper if process cannot be converted automatically
def p[A](x: A)(implicit instance: ToProcess[A]): Process = instance.convertToProcess(x)
def par[A: ToProcess, B: ToProcess](x: A, y: B): Process = par(p(x), p(y))
// Tuples defined as infix function e.g. (a ~ b)
def t[A: ToProcess, B: ToProcess](x1: A, x2: B): Process = tuple(p(x1), p(x2))
// Extensions
def send[A: ToProcess](x: A): Process = send(p(x))
def receive[A: ToProcess](x: A): Process = receive(p(x))
def sys[A: ToProcess](id: String, proc: A): Process = sys(id, p(proc))
/**
* Reductions of our super Simple Rholang language.
* When matching Send/Receive are found they are erased.
*/
def eval(proc: Process): Process =
proc match {
case Par(state) =>
val procs = state.foldLeft(Set[Process]()) {
// Reduction of Send if matches Receive
case (acc, send @ Send(name)) =>
if (state.contains(Receive(name))) acc else acc + send
// Reduction of Receive if matches Send
case (acc, rec @ Receive(name)) =>
if (state.contains(Send(name))) acc
else acc + rec
// System processes
case (acc, GSystem(id, arg)) if id == "rho:io:stdout" =>
// Standard output
println(s"> ${arg}")
acc
// For all other combinations we do nothing
case (acc, p) => acc + p
}
Par(procs)
case p => p
}
}
import rholang._
// Implicit conversion for Process (identity)
implicit object ProcessIdentity extends ToProcess[Process] {
def convertToProcess(x: Process): Process = x
}
// Implicit conversion for String
implicit object StringToProcess extends ToProcess[String] {
def convertToProcess(x: String): Process = string(x)
}
// Implicit conversion for Int
implicit object IntToProcess extends ToProcess[Int] {
def convertToProcess(x: Int): Process = int(x)
}
// Binary infix functions
implicit class syntaxRholang[A](private val p1: A) extends AnyVal {
// Defines pipe `|` as nicer syntax for `par`
// `|` is not the best choice, already defined for numbers. `||` maybe?
// a | b ==> par(a, b)
// a | b | c ==> par(par(a, b), c)
def |[B: ToProcess](p2: B)(implicit to: ToProcess[A]): Process = par(p1, p2)
// Defines tilde `~` as nicer syntax for tuple (how to lift Scala tuple?)
// a ~ b ==> t(a, b)
// a ~ b ~ c ==> t(t(a, b), c)
def ~[B: ToProcess](p2: B)(implicit to: ToProcess[A]): Process = t(p1, p2)
}
/**
* Our first Simple Rholang program.
*/
val prog1: Process = {
val p1 = send(nil | 42 | "A")
val p2 = send("B")
val p3 = receive("B")
val p4 = send("C")
val p5 = sys("rho:io:stdout", "Special channel!" ~ 42)
val p6 = receive("A" | 42)
p1 | p2 | p3 | p4 | p5 | p6 | nil
}
// prog1: rholang.Process = Send("B") | Receive("B") | Send("C") | Send(42 | "A") | Receive("A" | 42) | SYS(rho:io:stdout, ("Special channel!", 42)})
val prog2 = "Process String in par with Int" | 42
// prog2: rholang.Process = "Process String in par with Int" | 42
/**
* Evaluate the program and get remaining processes (state inside Par).
*/
val res = eval(prog1)
// > ("Special channel!", 42)
// res: rholang.Process = Send("C")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment