• 2020年7月24日 16:10

    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微信,拉你入群。非开发者请勿加,会有一轮测试,通过者方可入群。