回顾计算机理论发展史,现今几乎所有软件都基于上世纪的理论:上世纪30年代发展的图灵机理论,将程序与数据分离,程序根据当前状态输出下一个状态。- @4 @& p5 s* Q# i+ c3 \ ' q3 D5 F9 h6 t. N! A0 G; {6 S# V 和图灵机理论等价的lambda演算(λ-calculus),对函数调用进行聚合,现今几乎所有的编程语言都基于这一理论模型。接下来40年代发展出的冯诺依曼体系架构,使用随机存储器(内存)来保存程序状态。; ^& K' K5 O; d 7 e# l2 i& B8 y) r; q/ C& k: b, i这些理论奠定了当今主流的计算机模型-冯诺依曼计算机模型。发展到今天,即使在引入线程、纤程、mailbox、channel、异步等概念后,并没有从根本上改变其顺序执行的本质, 其在大规模分布式并行执行的环境下(比如区块链网络)相形见绌。0 ]/ @4 o* B0 W' u: m 前人早已洞悉到了这些问题并逐渐发展出了一些以并行计算作为着眼点的形式系统。 其中以Actor模型和process-calculus最为著名。RChain的分布式并行计算模型来自于Rho-caculus, 它是process-calculus的分支。它的发展脉络可以从下图中了解到。1 D8 S2 A( W$ h0 X 4 ?4 S/ G7 y k) s- s$ G* bλ演算(Lambda-Calculus)最早是由Alonzo Church(图灵的博导)在20世纪30年代引入,当时的背景是解决函数可计算的本质性问题,初期λ演算成功的解决了在可计算理论中的判定性问题。后来根据Church–Turing thesis,证明了λ演算与图灵机是等价的。Lambda演算可比拟是最根本的编程语言,它是现今几乎所有编程语言(C#、Java、Python、Erlang、Scala、Haskell等)的理论基础。, l5 `' B3 o' b+ ~% |, v" `" a 3 g1 W5 Y u1 K- b1 w 随后在上世纪80年代,Robin Milner、Tony Hoare(快速排序算法的发明者)、Jan Bergstra以及Jan Willem Klop等科学家通过对并行计算进行建模,发展了Process-calculus。 6 u0 j g1 l, ^+ V ( v" e3 v' }+ y0 K7 E而后的90年代以Robin Milner为代表的科学家继续在此基础上发展出来了π-calculus。π-calculus是第一个成功实现在动态变化的网络环境中进行并行计算的理论模型。这即是说,在多个计算节点组成的网络中,即使计算过程中有节点加入或者离开网络,计算过程也能平滑地在网络上继续下去。从这点上来说,它非常适合于区块链网络这种的多变的网络环境。6 ~, F# Q% U# A. g3 |) i4 y0 c 3 v, T; q' R% i U. K21世纪初期以来开始发展的Reflective Higher-Order calculus和π-calculus一脉相承, 它在π-calculus的基础上加入了反射(reflection), 是第一种支持反射的并行计算模型。 注意这里所说的反射不同于JAVA/C#程序员熟悉的结构化反射,具体会在后面说明。 ?) G6 [6 {$ a& Y% o5 I! B1 o9 i( K; L& G7 t5 r& Z5 Y; ~- J# h4 ]& W 2、Rho演算(Rho-Calculus)7 w5 z {& m/ c* I G1 R$ ^5 m0 q RChain的编程语言Rholang衍生自Rho-calculus,自然地继承了Rho-calculus的四个C属性: * y- n6 d8 ?1 [% x, `7 T& M+ P+ i3 B * A; c$ V: w) j; xCompleteness 图灵完备. m2 D. J' X8 C/ V Complexity 有确切的时间、空间复杂度) W: \' O/ P7 t( m Concurrency 并行性4 t0 x* W% A6 h Compositionality 可组合性 $ D- @% ?, B- Y0 k2 {$ }! p& F2 G Rho-calculus可以用下面的项(term)进行表述( k. o; i% d7 o/ X5 L' O P, Q, R ::= 0 ' }7 b% Y7 ]" i, ] | for( ptrn1 ! I3 a/ g2 C5 a接下来一项一项对照代码解释。, N2 Y" H2 b. G 2.1 Rholang的世界观7 ]9 L8 p" a( Q: [ 假想一辆自动驾驶的汽车, 传感器监控着道路、速度、方向等参数, 行车电脑根据它们控制着方向、油门刹车。而它们又影响着转向机构、变速箱、发动机等等, 在它们的内部又是各种机械结构相互作用最终决定汽车的行进。8 e2 l R/ N& \3 _% k( {' P 在这个复杂系统中,所有的参与者都是process, 而各部分通过消息相互作用,组成一个有机的整体。 以这样的眼光看世界的话,随会发现世界也是由通过消息相互影响的process构成。5 a2 G3 U" X9 n3 a7 r; S% y' M( O9 o 在Rholang的世界中,亦是如此: + p( q+ c& B( Q0 G* t' i, g 5 A0 u Q* M7 A5 L5 J一切皆是process/ T. Y& b( J- S, B1 K; Y process通过消息进行通信% I1 D, ~) G t ' p7 u) f! l3 g! i1 I 2.2 Process和Quoted Process/ _; a7 [7 ]1 o% w. h9 E 有如下代码 + r6 q+ O8 ^1 H9 F' B0 \* V0 n2 }7 Y; X( Q8 T; {8 T& w$ m <div>new myName in { , a. y& h6 e N& y7 ]1 @8 \6 t5 i5 p# E Nil ( S! c! d7 Y% `}</div>复制代码: P3 ~* W/ c6 P% \" q 9 [* v& E8 e! n/ K( T Nil 是最简单的process,它表示一个已经停止的process(就是啥都不做),对应rho-calculus中的0# O: p8 e9 D7 v* ~ new myName in申明一个quoted process,也叫做name。其中myName是这个name的标识符,对应rho-calculus中的 @P。 它的作用域仅在后面的花括号范围内。& g. w7 k7 W, h & D. ]+ R' S6 Y& }2.3 发送消息 8 T) F! G5 h. i; {% ?name(quoted process)提供了消息的通信机制作为channel使用,可以将它视为一个无序队列(Unordered Queue)。8 ~. |- U% \* I, r" o, t 通过!操作,可以向这个channel发送一个process作为消息。 比如如下代码将Hello World发送到myName中。发送的必须是一个process,而一切都是process,当然也包括字符串。 2 @* E6 k" P0 }8 ^+ R 0 r4 e9 i, \' a/ Z<div>new myName in {8 u8 t5 f5 w( ]7 R ]3 ?9 c myName!("Hello World")& M0 g& F4 c& ?2 F! u! H1 r }</div>复制代码 0 J. M" p1 M! m+ S' ~) p# B/ i+ q( n * C$ O/ t9 m% L( c* F4 X0 k2.4 接收消息1 Z% x. K# T" [ 而通过操作,可以从channel接收并删除一个消息,接收到的消息是一个name(quoted process)4 v8 l! I6 F t6 T2 I9 t new myName in { & F" j" N% ^6 |1 A! @ for( v- \$ t% O2 f5 \4 X4 q; f, p; q for后面的花括号是一个在接受到消息后执行的process,称之为continuation。 7 `4 T' L& ?% @4 P: a0 D t @! C: ^0 K- P: u9 O7 z" i2.5 并行执行 & o5 o' X; [: G# O* c3 p0 t在Rholang的世界中,是不存在顺序执行的。( W# D/ s7 e6 S 如果需要先执行完A然后执行B,那么A和B之间一定有某种数据依赖,需要通过上面的消息机制实现。 # i# N+ ]4 I/ g1 ^$ j一般地,通过P|Q操作并行地执行多个process。 比如:) J; W8 ~! a5 `/ \ new myName in {$ l$ a5 V g8 N7 T; o8 S for( v 5 H' \/ J- X7 R3 s+ v: X0 s这段代码并行执行了两个process:其中一个process向myName发送了"Hello World"字符串;另一个process从myName读取一条消息后啥也不做。' O' O/ }' E* ]) C3 ]0 L* k 6 Y% c( G7 A8 }( Y 2.6 Reflect和Reify O' G+ G/ Q# Z% x/ h* z7 g 上面讲到的几个基本操作实际上都继承自π-calculus。 那么剩下的两个操作,就是rho-calculus对π-calculus的发展。 4 q& I9 q2 Y/ S5 L在rho-calculus中, name(quoted process)是process的引用,它代表的是指向的process的代码,亦既process的语法结构(syntactic structure), G/ F! r# S, q' \1 h3 D可以简单地类比为磁盘上的可执行文件; , s# y- r" }- |9 V4 V* ?/ z而process是一个可运行的实例,类比于执行文件启动后创建的进程,是一个动态的概念。 ' [5 W+ A6 M% E6 h3 u! Z+ _; }, b& s通过* 和 @ 两个操作符可以让name(quoted process)和process相互转换,如下图。2 C! Q; e/ U( j6 x. G " B. t5 r) y/ a2 s6 z' @ *x Reify操作将name(quoted process)反序列为process ' s+ A. L- v7 `+ M1 ^. |" O" G@P Reflect操作将process序列化为相应的name(quoted process) / l2 ^' w) }, W7 S8 s* }% N: w # G3 K |) I& L* q v& D例1: ) w- ~$ U7 a9 Z, k@"stdout"!("Hello World") / _4 W4 T3 {$ W* t5 n如前文所说,一切都是process。那么"stdout"和"Hello World"都是process。将"Hello World"这个process发送给"stdout"肯定是不行的。 ! `. g+ a! |3 P- d9 f! g因为根据x!(Q),发送的目的必须是一个name。因此@"stdout"操作将"stdout"这个process转换成了相应的name。实际上这就是一个系统合约:使用标准输出返回消息。 7 O& }5 K1 z" u* m6 Z2 C 1 @$ v9 k2 [ E0 l4 T* } h例2:7 S1 W4 K1 R! q: S+ t) L new myName in {: q5 o4 {$ O, ^' D: q: g$ N for( v " I+ P9 {; c9 Y从myName中获取的消息以v标识,根据前面的文法for( ptrn1 ,v一定是一个name。 , l) r2 h4 `- D/ K" p而接下来需要将v发送到@"stdout",又根据x!(Q)发送的必须是process而不能是name,因此需要*v装换成了相应的process。5 G7 [! g8 G! G O' I! k* L " z: r$ S1 z1 [% I& H2 k2 c" Y 例3:$ ~& g9 h: z: Z2 l3 ? new myName in { * N4 y, t4 g1 i @ for( @v- r2 y" H( J9 u" A: F' @ 这段代码和例2的代码非常类似。不同的是,接受到的消息用@v标识符表示,那么这里v是一个process,因此可以直接发送给@"stdout" 3 a0 d6 p6 b4 D*x和@P两个操作的加入让rholang具备了反射的能力。这里所说的反射不同于JAVA/C#程序员熟悉的元编程(meta-programming)。; B1 x& a$ V2 ^% e6 ]% S rholang的反射是一种过程化的反射,它使得process具有迁移到网络上其它节点执行的能力,因此Rho-calculus还有个别名叫移动进程演算(Mobile process calculus)。 1 e3 K, n, J! b( h: b. U+ I/ c3 `* h 8 |5 r. }$ r# i7 u; m) ?6 V例4:0 \2 {1 k( L" t new myName in { - l/ U; \4 B) t) \/ w( l$ N for( v7 C1 u( a W) ~5 d/ z* t; ?9 ?2 N9 q 这段代码将{ @"stdout"!("Hello World") }这个process发送到了myName,另一个process在接收到后执行该process。 9 H+ |5 m/ L+ `) |7 y需要注意的是myName!({ @"stdout"!("Hello World") })和for( v 这两个process是可以在不同的网络节点上执行。' E" D$ {# U8 h; e0 j- n q3 ^ 但是对于rholang程序开发者是透明的。这就是rholang的可迁移性(Mobility),它使Rholang程序在RChain的网络上“流动”,具体的细节在后文名字空间一节中详述。