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)
其中:
- PART_EXPR 是确定参与者的表达式或是竞争表达式。
- ID_0 到 ID_n 是用于 PART 的公有本地状态的标识符。
- PAY_EXPR 是用于确定支付金额的公有表达式。
- WHEN_EXPR 是一个计算出布尔值的公有表达式,用于判断是否执行共识转移。
- DELAY_EXPR 是一个公有表达式,它只依赖于共识状态,并计算出一个由自然数表示的时间增量。
- TIMEOUT_BLOCK 是一个超时代码块,它将在 DELAY_EXPR 计算出的单位时间过后执行,这一单位时间从上一个共识步骤结束开始计算,PART执行当次共识转移的时间不算在内。
共识转移语句的延拓部分是一个共识步骤,以一个 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();
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 /> })<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 值参数化;
- 以及,timeout 和 throwTimeout 参数与共识转移中的同名参数相同。
若 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 的人不仅决定值(以及支付金额),而且还决定了共识步骤代码运行什么来消耗值。
// 首先我们那定义一个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,用以表示所有参与者之间的竞争。
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 参数是可选项。