5.4.3 步骤

Reach 的步骤存在于 Reach.App 主体部分,或是 commit 语句延拓部分。它表示应用程序中每个参与者所执行的操作。

5.4.3.1 语句

任何对计算有效的语句都对步骤有效。但也允许一些额外语句。

5.4.3.1.1 only 和 each

Alice.only(() => {
const pretzel = interact.random(); });

本地步骤格式为 PART.only(() => BLOCK) ,其中 PART 是一个参与者标识符,BLOCK 是一个代码块。在 BLOCK 内部,PART 与参与者的地址绑定。 任何在本地步骤代码块中定义的绑定关系,都可以作为新的本地状态语句尾部使用。例如:

Alice.only(() => {
const x = 3; });
Alice.only(() => {
const y = x + 1; });

是一个有效程序,其中 Alice 的本地状态包含私有值 x(与3绑定)和 y(与4绑定)。然而,此类绑定不属于共识状态,是纯粹的本地状态。例如:

Alice.only(() => {
const x = 3; });
Bob.only(() => {
const y = x + 1; });

是一个无效程序。因为 Bob 并不知道 x 是什么。

each([Alice, Bob], () => {
const pretzel = interact.random(); });

each 本地步骤语句格式为 each(PARTTUPLE()[ => ](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28~3d.~3e%29%29%29)BLOCK) ,其中 PART_TUPLE 是参与者元组,BLOCK 是一个代码块。each 语句是一系列可以用 [only](https://docs.reach.sh/ref-programs-step.html#%28reach.%28%28only%29%29%29) 语句来写的本地步骤的缩略形式。

5.4.3.1.2 支付金额

支付金额可以是下述情形之一:

代币金额可以是下述情形之一:

例如,以下均是支付金额:

0
5
[ 5 ]
[ 5, [ 2, gil ] ]
[ [ 2, gil ], 5 ]
[ 5, [ 2, gil ], [ 8, zorkmids ] ]

对于同一个支付金额而言,重复声明一个代币数量是无效的。例如,以下均是无效支付金额

[ 1, 2 ]
[ [2, gil], [1, gil] ]

5.4.3.1.3 publish,pay,when,和 timeout

Alice.publish(wagerAmount)
.pay(wagerAmount)
.timeout(DELAY, () => {
Bob.publish();
commit();
return false; });
Alice.publish(wagerAmount)
.pay(wagerAmount)
.timeout(DELAY, () => closeTo(Bob, false));
Alice.publish(wagerAmount)
.pay(wagerAmount)
.timeout(false);

共识转移语句格式为:
PARTEXPR.[publish](https://docs.reach.sh/ref-programs-step.html#%28reach.%28%28publish%29%29%29)(ID0,…,ID_n).[pay](https://docs.reach.sh/ref-programs-step.html#%28reach.%28%28pay%29%29%29)(PAYEXPR)..when(WHEN_EXPR).[timeout](https://docs.reach.sh/ref-programs-step.html#%28reach.%28%28timeout%29%29%29)(DELAYEXPR,()[=>](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28~3d._~3e%29%29%29)TIMEOUT_BLOCK)
其中:

共识转移语句延拓部分是一个共识步骤,以一个 commit 语句结束。超时代码块的延拓部分与存在超时的函数的延拓部分相同。

阅读关于非参与者的导览章节,理解何时使用、以及如何最有效地使用超时

若在本次共识转移中,发布网络代币转移两者任一未发生,则可以省略 publish 组件异或 pay 组件。若条件假定为 true,那么 when 组件总是可以省略。publish 或是 pay 必须首先生成,其后的组件可以任意顺序生成。例如,以下均有效

Alice.publish(coinFlip);
Alice.pay(penaltyAmount);
Alice.pay(penaltyAmount).publish(coinFlip);
Alice.publish(coinFlip)
.timeout(DELAY, () => closeTo(Bob, () => exit()));
Alice.pay(penaltyAmount)
.timeout(DELAY, () => {
Bob.publish();
commit();
exit(); });
Alice.publish(bid).when(wantsToBid);

当 when 组件并不是恒为 true 时,timeout 组件必须存在。这确保了你的客户端能够最终完成程序的执行。若共识转移确定会在一个非类参与者与一个可能会尝试(即不恒为 false )进行转移的参与者类之间竞争产生,那么 timeout 可以通过 .timeout(false) 的形式显式省略。

.throwTimeout 可以用来代替 .timeout。它接受一个 DELAYEXPR 和一个 EXPR ,发生超时就会抛出。如果未给定 EXPR ,则会抛出 [null](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28null%29%29%29) 值。若有共识转移使用 .throwTimeout,它必须被包含在 try 语句中。

若一个共识转移明确指定单个参与者,而该参与者既没有在应用中被固化,又不是一个参与者类,那么该语句会做这件事。也就是说,这之后 PART 可以通过一个地址来调用。

若一个共识转移明确指定单个参与者类,那么该类的所有成员都会尝试执行转移,但是只有一个会成功。

共识转移将所有参与者标识符 ID_0 到 ID_n 与共识转移中包含的值进行绑定。若存在一个参与者之前绑定了标识符,但未被包含在 PART_EXPR 中,则该程序无。换句话说,以下程序无效:

Alice.only(() => {
const x = 1; });
Bob.only(() => {
const x = 2; });
Claire.only(() => {
const x = 3; });
race(Alice, Bob).publish(x);
commit();

因为 Claire 没被包含在 race 内。然而,若我们把 Claire 的 x 重命名为 y,那就是有效的。虽然 Alice 和 Bob 都绑定了 x,但他俩都参与了 race,这使得程序有效。在这个程序的尾部,x 被绑定到了 1 或 2。

5.4.3.1.4 fork

fork()
.case(Alice, (() => ({
msg: 19,
when: declassify(interact.keepGoing()) })),
((v) => v),
(v) => {
require(v == 19);
transfer(wager + 19).to(this);
commit();
exit();
})
.case(Bob, (() => ({
when: declassify(interact.keepGoing()) })),
(() => wager),
() => {
commit();

  1. Alice.[only](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28only%29%29%29)(() [=>](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28~3d._~3e%29%29%29) [interact](https://docs.reach.sh/ref-programs-local.html#%28reach._%28%28interact%29%29%29).showOpponent(Bob));
  2. [race](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28race%29%29%29)(Alice, Bob).[publish](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28publish%29%29%29)();<br /> [transfer](https://docs.reach.sh/ref-programs-consensus.html#%28reach._%28%28transfer%29%29%29)(2 [*](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28%2A%29%29%29) wager).to([this](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28this%29%29%29));<br /> [commit](https://docs.reach.sh/ref-programs-consensus.html#%28reach._%28%28commit%29%29%29)();<br /> [exit](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28exit%29%29%29)();<br /> })<br />.[timeout](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28timeout%29%29%29)(deadline, () [=>](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28~3d._~3e%29%29%29) {<br /> [race](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28race%29%29%29)(Alice, Bob).[publish](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28publish%29%29%29)();<br /> [transfer](https://docs.reach.sh/ref-programs-consensus.html#%28reach._%28%28transfer%29%29%29)(wager).to([this](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28this%29%29%29));<br /> [commit](https://docs.reach.sh/ref-programs-consensus.html#%28reach._%28%28commit%29%29%29)();<br /> [exit](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28exit%29%29%29)(); });

fork 语句格式为:
fork()
.case(PARTEXPR,
PUBLISH_EXPR,
PAY_EXPR,
CONSENSUS_EXPR)
.[timeout](https://docs.reach.sh/ref-programs-step.html#%28reach.
%28%28timeout%29%29%29)(DELAYEXPR, () [=>](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28~3d._~3e%29%29%29)
TIMEOUT_BLOCK);
其中:

  • PART_EXPR 是确定参与者的表达式;
  • PUBLISH_EXPR 是一个语法箭头表达式,它在指定参与者本地步骤中被计算,且该表达式必须得出一个对象。该对象包含 msg 和 when 两个字段,msg 字段可能是任何类型,而 when 字段必须是一个布尔值。
  • PAY_EXPR 是一个表达式,该表达式通过 msg 值参数化的函数确定值,返回一个支付金额
  • CONSENSUS_EXPR 是一个语法箭头表达式,该表达式通过在共识步骤中计算的 msg 值参数化;
  • 以及,timeoutthrowTimeout 参数与共识转移中的同名参数相同。

若 PUBLISHEXPR 返回的对象中缺少 msg 字段,则 msg 字段被视为 [null](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28null%29%29%29)。

若 PUBLISHEXPR 返回的对象中缺少 when 字段,则 when 字段被视为 [true](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28true%29%29%29)。

若缺少 PAYEXPR,则被视为 ()[ => ](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28~3d._~3e%29%29%29)0。

.case 组件可以重复多次。

同一个参与者可以被多个 case 指定。在这种情况下,case 的顺序至关重要。也就是说,只有当前一个 case 的 when 字段为 false 时,才会对后续 case 进行处理。

如果由 PARTEXPR 指定的参与者未被固化(从 [Participant](https://docs.reach.sh/ref-programs-module.html#%28reach.%28%28.Participant%29%29%29).set 的意义上来说),在它赢得竞争时被固化,前提它不是一个参与者类

fork 语句是通用的 race switch 模版的缩写,您也可以自己编写。

其思想是,case 组件中的每个参与者对他们想要 publish 的值进行独立的本地步骤计算,然后所有参与者 race 争取 publish。赢得 race 的人不仅决定值(以及支付金额),而且还决定了共识步骤代码运行什么来消耗值。

有关 fork 关键词的简单 fork 语句大致如下:

// 首先我们那定义一个Data实例,这样每个参与者都可以发布一个不同的值
const ForkData = Data({Alice: UInt, Bob: Null});
// 然后我们为每个参与者绑定值
Alice.only(() => {
const forkmsg = ForkData.Alice(19);
[const](https://docs.reach.sh/ref-programs-compute.html#%28reach.
%28%28const%29%29%29) forkwhen = [declassify](https://docs.reach.sh/ref-programs-local.html#%28reach.%28%28declassify%29%29%29)(interact.keepGoing()); });
Bob.only(() => {
const forkmsg = ForkData.Bob([null](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28null%29%29%29));
const forkwhen = [declassify](https://docs.reach.sh/ref-programs-local.html#%28reach.%28%28declassify%29%29%29)(interact.keepGoing()); });
// 参与者竞争
race(Alice, Bob)
.publish(forkmsg)
.when(fork_when)
// 支付金额由发布的一方决定
.[pay](https://docs.reach.sh/ref-programs-step.html#%28reach.
%28%28pay%29%29%29)(forkmsg.[match](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28match%29%29%29)( {
Alice: (v => v),
Bob: (() => wager) } ))
// 超时部分总是一样的
.timeout(deadline, () => {
race(Alice, Bob).publish();
transfer(wager).to(this);
commit();
exit(); });

// 确保正确的参与者发布正确类型的值
require(forkmsg.[match](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28match%29%29%29)( {
// Alice 之前已经发布过
Alice: (v => this == Alice),
// 但是 Bob 没有
Bob: (() => true) } ));

// 然后我们选择合适的主体来运行
switch (forkmsg) {
[case](https://docs.reach.sh/ref-programs-compute.html#%28reach.
%28%28case%29%29%29) Alice: {
assert (this == Alice);
require(v == 19);
transfer(wager + 19).to(this);
commit();
exit(); }
case Bob: {
Bob.set(this);
commit();

 Alice.[only](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28only%29%29%29)(() [=>](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28~3d._~3e%29%29%29) [interact](https://docs.reach.sh/ref-programs-local.html#%28reach._%28%28interact%29%29%29).showOpponent(Bob));

 [race](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28race%29%29%29)(Alice, Bob).[publish](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28publish%29%29%29)();<br />     [transfer](https://docs.reach.sh/ref-programs-consensus.html#%28reach._%28%28transfer%29%29%29)(2 [*](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28%2A%29%29%29) wager).to([this](https://docs.reach.sh/ref-programs-compute.html#%28reach._%28%28this%29%29%29));<br />     [commit](https://docs.reach.sh/ref-programs-consensus.html#%28reach._%28%28commit%29%29%29)();<br />     [exit](https://docs.reach.sh/ref-programs-step.html#%28reach._%28%28exit%29%29%29)(); }<br /> }

这种模版编写起来不仅繁琐而且容易出错,因此 fork 语句为 Reach 程序员简化了它。当一个参与者指定了多个实例时,参与者的 msg 字段将被一个额外的变量包装,用以表示选择了什么实例。

5.4.3.1.5 wait

wait(AMOUNT);

写作 wait(AMOUNT); 的 wait 语句用以延迟计算,直到时间增量值流逝。该语句只存在于一个步骤中。

5.4.3.1.6 exit

exit();

写作 exit(); 的 exit 语句停止计算。这是终止语句,因此必须有一个空尾。该语句只存在于一个步骤中。


5.4.3.2 表达式

任何对计算有效的语句都对步骤有效。但也允许一些额外表达式。

5.4.3.2.1 race

race(Alice, Bob).publish(bet);

写作 race(PARTICIPANT0,…,PARTICIPANT_n); 的表达式构造了一个参与者,该参与者可用于共识转移语句中,例如 [publish](https://docs.reach.sh/ref-programs-step.html#%28reach.%28%28publish%29%29%29) 或 pay。该表达式中的各参与者相互竞争,获胜者执行共识转移

Reach 提供了一个缩略写法 Anybody,用以表示所有参与者之间的竞争。

阅读关于竞争的导览章节,理解使用 race 的益处与风险。

5.4.3.2.2 unknowable

unknowable( Notter, Knower(var_0, …, var_N), [msg] )

这是一种知识断言,表示参与者 Notter 不知道变量 var_0 到 var_N 的结果,但参与者 Knower 知道这些值。它接受一个可选的字节参数,该参数会被包含在所有报告的冲突中。


5.4.3.2.3 closeTo

closeTo( Who, after )

让参与者 Who 制作发布,然后将 balance() 转移给 Who,并在步骤中执行 after 函数后结束 DApp。after 参数是可选项。