RChain 的开发语言是 Scala, Greg 是Scala语言的早期参与者,2010年 Greg 出版过一本 Scala 教程 “Pro Scala: Monadic Design Patterns for the Web”,www.goodreads.com/book/show/8266596-scala
这本教程还被 wikipedia 引用,我在 wikipedia 的附注里发现了这本书的电子版,就在 Greg 的 github 代码库里:github.com/leithaus/XTrace/blob/monadic/src/main/book/content/monadic.pdf
Rholang 是 RChain 的智能合约语言,最大特色是处处并发。Rholang 的解释器是 Scala 实现的,但是太复杂,不便于理解。今天我们从 Tomislav 这段 simple Rholang 来管窥一下 Rholang 的精髓:par 是怎么实现的。simpleRholang 是 Tomislav 主持的 RChain 社区学习系列的一个主题。
1. Rholang的基本语句
a!(1)|
for(b <-a){
stdout!(["I got", *b])
}
这段代码的意思是在名字为a的channel里放入1,当有另一个进程在a的channel等待消息时,它们就完成了一个comm event,并且触发continuation的执行,也就是在屏幕打印["I got", 1]
par表示并发,par指代码中的“|”符号。par连接的语句不分先后,哪个先执行是随机的。send即代码中的“!”,receive即代码中的“for”。send和receive无论哪个先执行,都会处于等待状态,直至有一个可以配的进程出现。
2. simpleRholang要完成的事
simpleRholang 目标很简单,就是实现 par 功能,当
send("a") | receive("a")
时,则reduce。simpleRholang 并没有实现 continuation。
3. simpleRholang的程序结构
程序可三部分:
- 定义par, send, receive
- 定义 eval,对par, send, receive 进行 eval
- 对以上代码进行测试
第一部分: par, send, receive
原代码中有很多注释,原代码在本文的最后部分,大家可以参考。
trait Process //定义了一个简单的接口
private final case class Par(s: Set[Process] = Set()) extends Process
private final case class Send(name: String) extends Process
private final case class Receive(name: String) extends Process
def nil: Process = Par(s = Set()) // 定义了nil, 显然 par 是一个 monoid
def par(p1: Process, p2: Process = nil): Process = (p1, p2) match {
case (Par(s1), Par(s2)) => Par(s1 ++ s2) // nil | p == p == p | nil
case (Par(s1), p2) => Par(s1 + p2) // Par(Set(Par(Send(A)))) => Par(Set(Send(A)))
case (p1, Par(s2)) => Par(s2 + p1)
case (p1, p2) => Par(Set(p1, p2)) // 其它情况都放入Set
} // 定义了monoid的结合律
def send(name: String): Process = par(Send(name))
def receive(name: String): Process = par(Receive(name))
第二部分:eval
用eval对输入的进程进行reduce
def eval(proc: Process): Process =
proc match {
case Par(state) =>
val procs = state.foldLeft(Set[Process]()) {
// 如果Send和Receive匹配上,就做reduction
case (acc, send @ Send(name)) =>
if (state.contains(Receive(name))) acc
else if (name.startsWith("OUT")) {
// 专门定义了一个"OUT",用于测试
println(name)
acc
} else acc + send
// 如果Receive和Send匹配上,就做reduction
case (acc, rec @ Receive(name)) =>
if (state.contains(Send(name))) acc
else acc + rec
case (acc, p) => acc + p // 其它情况不做处理
}
Par(procs)
case p => p
}
第三部分:测试
import rholang._
// 用"|"来表示par
implicit class syntaxRholang(private val p1: Process) extends AnyVal {
def |(p2: Process): Process = par(p1, p2)
}
val prog1: Process = {
val p1 = send("A")
val p2 = send("B")
val p3 = receive("B")
val p4 = send("B")
val p5 = send("OUT: Special channel!")
val p6 = receive("C")
p1 | p2 | p3 | p4 | p5 | p6 | nil
}
// 运行结果 prog1: rholang.Process = Par(Set(Send(B), Receive(B), Receive(C), Send(OUT: Special channel!), Send(A)))
val prog2 = send("A")
val prog3 = receive("B") | send("A")
// Structural equivalence
val prog4 = send("A") | nil
val prog5 = nil | send("A")
val progEqual_2_4_5 = (prog2 == prog4, prog4 == prog5)
val prog7 = send("A") | send("A")
// 可以分别对不同的prog 进行eval
val res = eval(prog1)
// OUT: Special channel!
// res: Process = Par(Set(Receive(C), Send(A)))
完整代码如下,代码原址: gist.github.com/tgrospic/c56316dd769fdb797337dbeb5047e794
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
private final case class Send(name: String) extends Process
private final case class Receive(name: String) 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: String): Process = par(Send(name))
def receive(name: String): Process = par(Receive(name))
/**
* 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 if (name.startsWith("OUT")) {
// If special name do something
println(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
// For all other combinations we do nothing
case (acc, p) => acc + p
}
Par(procs)
case p => p
}
}
import rholang._
// Defines | as `par` for nicer syntax
implicit class syntaxRholang(private val p1: Process) extends AnyVal {
def |(p2: Process): Process = par(p1, p2)
}
/**
* Our first Simple Rholang program.
*/
val prog1: Process = {
val p1 = send("A")
val p2 = send("B")
val p3 = receive("B")
val p4 = send("B")
val p5 = send("OUT: Special channel!")
val p6 = receive("C")
p1 | p2 | p3 | p4 | p5 | p6 | nil
}
// prog1: rholang.Process = Par(Set(Send(B), Receive(B), Receive(C), Send(OUT: Special channel!), Send(A)))
val prog2 = send("A")
val prog3 = receive("B") | send("A")
// Structural equivalence
val prog4 = send("A") | nil
val prog5 = nil | send("A")
val progEqual_2_4_5 = (prog2 == prog4, prog4 == prog5)
val prog7 = send("A") | send("A")
/**
* Evaluate the program and get remaining processes (state inside Par).
*/
val res = eval(prog1)
// OUT: Special channel!
// res: Process = Par(Set(Receive(C), Send(A)))
欢迎关注 “Rholang中文社区” 公众号
欢迎开发者加入 “RChain开发者” 微信群。加群请加lowbird微信,拉你入群。非开发者请勿加,会有一轮测试,通过者方可入群。