- 5.4.6 计算
- 5.4.6.1 注释
- 5.4.6.2 块
- 5.4.6.3 语句
- 5.4.6.4 表达式
- 5.4.6.4.1 标识符引用
- 5.4.6.4.2 函数应用
- 5.4.6.4.3 类型
- 5.4.6.4.4 字面值
- 5.4.6.4.5 运算表达式
- 5.4.6.4.6 异或
- 5.4.6.4.7 带括号表达式
- 5.4.6.4.8 元组
- 5.4.6.4.9 数组
- 5.4.6.4.10 元素引用
- 5.4.6.4.11 数组和元组的长度:Tuple.length ,Array.length ,和 .length
- 5.4.6.4.12 数组和元组的更新:Tuple.set ,Array.set ,和 .set
- 5.4.6.4.13 可折叠操作
- 5.4.6.4.14 数组的组运算
- 5.4.6.4.15 映射的组运算
- 5.4.6.4.16 对象
- 5.4.6.4.17 结构体
- 5.4.6.4.16 字段引用
- 5.4.6.4.19 Object.set
- 5.4.6.4.20 Object.setIfUnset
- 5.4.6.4.21 Object.has
- 5.4.6.4.22 数据
- 5.4.6.4.23 Maybe
- 5.4.6.4.24 Either
- 5.4.6.4.25 match
- 5.4.6.4.26 条件表达式
- 5.4.6.4.27 箭头表达式
- 5.4.6.4.28 makeEnum
- 5.4.6.4.29 assert
- 5.4.6.4.31 possible
- 5.4.6.4.32 digest
- 5.4.6.4.33 balance
- 5.4.6.4.34 lastConsensusTime
- 5.4.6.4.35 makeDeadline
- 5.4.6.4.36 implies
- 5.4.6.4.37 ensure
- 5.4.6.4.38 hasRandom
- 5.4.6.4.39 compose
- 5.4.6.4.40 sqrt
- 5.4.6.4.41 pow
- 5.4.6.4.42 带符号整数
- 5.4.6.4.43 定点数
- 5.4.6.4.44 Anybody
- 5.4.6.4.45 ’use strict’
- 5.4.6.4.46 Intervals
5.4.6 计算
这一章节描述 Reach 中所有场景下均可用的常见特征。
5.4.6.1 注释
// 单行注释/ 多行 注释 */
注释是会被编译器忽略的文本。以 // 开头直到行尾的文本构成单行注释。用 / 和 / 闭合的文本构成多行注释。在多行注释中嵌套多行注释是无效的。
5.4.6.2 块
{ return 42; }{ const x = 31;
return x + 11; }{ if ( x < y ) {
return “Why”;
} else {
return “Ecks”; } }
块由一连串语句构成,这些语句被括号环绕,即 { 和 } 。
5.4.6.3 语句
本节描述在任何 Reach 场景中均可用的语句。
每一条语句都会影响后续语句的含义,语句的后续语句被称为其尾部。例如:若 {X;Y;Z;} 是一个块,那么 X 的尾部是 {Y;Z;} ,Y 的尾部是 {Z;} 。
从程序源代码的结构来看,尾部是静态可见的,而延拓则受函数调用的影响。
不以终止语句(没有尾部的语句)结尾的一系列语句,如 return 语句、 continue 语句或 exit 语句,均被视为以 return null; 结尾。
本节的剩余部分将列举每种语句。
5.4.6.3.1 const 和 function
标识符定义要么是值定义,要么是函数定义。每一个标识符定义均会引入一个或多个绑定标识符。
—
const DELAY = 10;const [ Good, Bad ] = [ 42, 43 ];const { x, y } = { x: 1, y: 2 };const [ x, [ y ] ] = [ 1, [ 2 ] ];const [ x, { y } ] = [ 1, { y: 2 } ];const { x: [ a, b ] } = { x: [ 1, 2 ] };
有效标识符遵循与JavaScript标识符相同的规则:它们可以由Unicode字母数字字符、或 _ 或 $ 组成,但不能以数字开头。
值定义的格式为:const LHS = RHS; 。
LHS必须遵从以下语法规定:
‹LHS› | ::= | ‹id› |
---|---|---|
| | [‹LHS-tuple-seq›] | |
| | {‹LHS-obj-seq›} | |
‹LHS-tuple-seq› | ::= | |
| | …‹LHS› | |
| | ‹LHS› | |
| | ‹LHS›,‹LHS-tuple-seq› | |
‹LHS-obj-seq› | ::= | |
| | …‹LHS› | |
| | ‹LHS-obj-elem› | |
| | ‹LHS-obj-elem›,‹LHS-obj-seq› | |
‹LHS-obj-elem› | ::= | ‹id› |
| | ‹propertyName›:‹LHS› | |
‹propertyName› | ::= | ‹id› |
| | ‹string› | |
| | ‹number› | |
| | [‹expr›] |
RHS 必须与 LHS 兼容。也就是说,如果 LHS 是一个 ‹LHS-tuple-seq› ,那么相应的 RHS 必须是一个元素数正确的元组。如果 LHS 是 ‹LHS-obj-seq› ,则相应的 RHS 必须是带有正确字段的对象。
—
function randomBool() {
return (interact.random() % 2) == 0; };
函数定义的格式为:
function FUN (LHS_0, …, LHS_n) BLOCK;
它将 FUN 定义为一个函数,该函数将其 BLOCK 块的函数主体抽象至左侧的 LHS_0 到 LHS_n 。
函数参数可以指定默认参数。用于实例化这些参数的表达式可以访问被定义的函数范围内的任意变量。此外,这些表达式可以引用函数定义中在它之前的参数。具有默认参数的参数必须位于所有其他参数之后。
function f(a, b, c = a + 1, d = b + c) =>
a + b + c + d;
—
Reach 程序中的所有标识符必须在程序中被绑定的位置解除绑定,即,用新定义对标识符进行跟踪处理是无效的。例如:
const x = 3;const x = 4;
是无效的。不管绑定关系是否仅为单个参与者所知,该限制均生效。例如,
Alice.only(() => {
const x = 3; });Bob.only(() => {
const x = 3; });
也是无效的。
特殊标识符 是此规定的例外。 总是被认为处于未绑定状态。这意味着 既是一个永远无法被读取的标识符,也是一个可以多次绑定的标识符。这在忽略不需要的值时比较有用,例如:
[const](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28const%29%29%29) [, x, ] = [1, 2, 3];
5.4.6.3.2 return
return 17;return 3 + 4;return f(2, false);return;
return 语句的格式为 return EXPR; 。其中 EXPR 是计算出与 EXPR 相等的值的表达式。作为特例, return; 与 return null; 的意思相同。
return 语句将其值返回给被环绕着的函数应用。
return 语句是终止语句,它必须有一个空尾。例如:
{ return 1;
return 2; }
5.4.6.3.3 if
if ( 1 + 2 < 3 ) {
return “Yes!”;} else {
return “No, waaah!”; }
这是一个条件语句,其格式为:
if (COND) NOTFALSE [else](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28else%29%29%29) FALSE
其中 COND 是一个表达式,NOTFALSE 和 FALSE 是语句(很有可能是语句块),执行 NOT_FALSE 还是 FALSE 取决于 COND 的值是否等于 [false](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28false%29%29%29)。
NOTFALSE 和 FALSE 均具有空尾,即NOT_FALSE 和 FALSE 的尾部均只限定在其语句内。例如:
[if](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28if%29%29%29) ( x < y ) {
const z = 3; }else {
const z = 4; }return z;
是错误的,因为标识符 z 并没有在条件语句之外被绑定。
若条件语句存在于共识步骤中,只能在 NOT_FALSE 或 FALSE 中包含共识转移。因为其语句与条件语句本身均处于相同的场景中。
5.4.6.3.4 switch
const mi = Maybe(UInt).Some(42);switch ( mi ) {
case None: return 8;
case Some: return mi + 10; }switch ( mi ) {
case None: return 8;
default: return 41; }
switch 语句格式为:switch (VAR) { CASE … },其中:VAR 是一个绑定数据实例的变量,CASE 要么是 case VARIANT: STMT …( VARIANT 是一个变量),要么是 default: STMT …,STMT 是一连串语句,根据 VAR 持有的变量选择对应的一连串语句。在 switch 的某个 case 主体部分,VAR 具备变量的类型。即在 Maybe(UInt) switch 的 一个 Some case 中,变量绑定了一个整型。
所有 case 均具有空尾,即 case 语句的尾部均只限定在该语句内。
若 switch 语句存在于共识步骤中,只能在 case 中包含共识转移。因为其语句与条件语句本身均处于相同的场景中。
同一 case 出现多次、或是 case 缺失、或是 case 多余(即某变量不存在于 VAR 的数据类型中),均是无效的。
5.4.6.3.5 块语句
当一个块出现在语句位置时,块语句为该块中的标识符定义建立一个局部的、单独的作用域。换句话说,含有块的代码段是整体运作的,但是块内语句的尾部与外围的尾部是隔离的。例如,
const x = 4;return x;
结果是 4 ,但是
{ const x = 4; }return x;
是错的,因为标识符 x 没有在块语句外部被绑定。
5.4.6.3.6 Try/Catch 和 Throw 语句
try {
throw 10;} catch (v) {
transfer(v).to(A); }
try 语句格式为:try BLOCK catch (VAR) BLOCK 。该语句允许在抛出异常时使用指定的处理程序执行代码块。
throw 语句格式为:throw EXPR ,该语句会将控制流传输到异常处理程序,并将 EXPR 和 VAR 绑定。可以存在的任何值均能被抛出。例如:Ints 和 Arrays 是可以被抛出的有效值。但函数不是。
5.4.6.3.7 表达式语句
4;f(2, true);
一个处于语句位置的表达式 E 等价于块语句 {return E; } 。
5.4.6.4 表达式
本节描述在任何 Reach 场景中均可用的表达式。Reach 程序中存在大量种类繁多的表达式。
本节的剩余部分将列举每种表达式。
5.4.6.4.1 标识符引用
XYZ
在本地步骤(即 only 或 each 表达式的主体部分)和共识步骤(即在 publish 或 pay 语句中,在 commit 语句之前)中,标识符 this 具有特殊含义。具体细节参见 this 和 this 。
5.4.6.4.2 函数应用
assert( amount <= heap1 )step( moveA )digest( coinFlip )interact.random()declassify( _coinFlip )
函数应用的格式为:EXPR_rator(EXPR_rand_0,…,EXPR_rand_n) ,其中:
EXPR_rator 和 EXPR_rand_0 到 EXPR_rand_n 都是用于计算某个值的表达式;
EXPR_rator 必须计算出 n 个相同类型的值,或是n个原语类型的参数;
扩展表达式(…expr)可能出现在函数应用的运算对象列表中,在这种情况下,expr 的元素会被拼接在恰当的位置。
new f(a) 等价于 f(a).new() ,在写面向类的程序时是一种非常方便的缩写。
5.4.6.4.3 类型
Reach 中的类型由以下标识符和构造函数表示:
- Null 。
- Bool ,表示一个布尔值。
- UInt ,表示一个无符号整型数。UInt.max 是指可以分配给 UInt 的最大值。
- Bytes(length) , 表示一个最大长度为 length 字节的字符串。
- Digest ,表示一个摘要。
- Address ,表示一个账户地址。
- Token ,表示一个非网络代币。
- Fun([Domain_0, …, Domain_N], Range) , 表示一个函数类型。
- Tuple(Field_0, …, FieldN) , 表示一个元组(参阅元组来构建元组 )。
- Object({key_0: Type_0, …, key_N: Type_N}) ,表示一个对象(参阅对象来构建对象)。
- Struct([[key_0, Type_0], …, [key_N, Type_N]]) ,表示一个结构体(参阅结构体来构建结构体)。
- Array(Type0,[size](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28size%29%29%29)) ,表示一个定长数组。 Type_0 必须是运行时可以存在的类型(即不是函数类型)(参阅数组来构建数组)。
- Data({variant_0: Type_0, …, variant_N: Type_N}) ,表示一个tagged union(或是汇总类型) (参阅数据来构建数据实例)。
- Refine(Type0, Predicate, [?](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28~3f%29%29%29)Message) ,其中 Predicate 表示精化类型,是一个返回布尔值的一元函数,即 Type0 的实例满足 Predicate 。当精化类型出现在被动位置(比如在一个 [is](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28is%29%29%29) 语句里面,或者是在一个作为参与者交互接口的 Fun 的域(Domain)中)时,它引入一个 assert;当它处于一个主动位置(即函数中的 Range )时,它引入了一个 assume 。Message 是一个可选字符串,在 Predicate 验证失败时显示。
比如,若 f 有如下类型:
Fun([Refine(UInt, (x => x < 5))], Refine(UInt, (x => x > 10)))
那么 const z = f(y) 等价于:
assert(y < 5);const z = f(y);assume(z > 10);
- Refine(Type0, PreCondition, PostCondition, [?](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28~3f%29%29%29)Messages) ,其中 Type0 是一个函数类型;PreCondition是一个一元函数,它接收域的元组并返回布尔值;PostCondition 是一个二进制函数,它接收域和范围的元组并返回一个布尔值;表示一个带有前置条件和后置条件的函数类型。前置条件是由 [assert](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28assert%29%29%29) 确保,后置条件则使用 assume 确保。Message 是可选的双元组字节。前置条件验证失败时显示第一条消息,后置条件验证失败时显示第二条消息。
比如 Refine(Fun([UInt, UInt], UInt),([x, y] => x < y),(([x, y], z) => x + y < z)) 是一个函数,要求其第二个参数必须大于第一个参数,并且结果必须大于输入。
Object 和 Data 普遍用于 Reach 中生效的代数数据类型。
typeOf(x) // typeisType(t) // Boolis(x, t) // Bool
typeOf 原语函数与 typeof: 相同,它返回其参数的类型。
若 isType 函数的参数是一个类型,则其返回 true。任何满足 isType 的表达式都会被编译掉,并且在运行时不存在。
若 is 函数的第一个参数是第二个参数的类型,则其返回 true。这在 Refine 中被称为被动位置。
5.4.6.4.4 字面值
100xdeadbeef007-1034.5432truefalsenull”reality bytes”‘it just does’
空字面值可以写作 null。
数字字面值可以是十进制、十六进制或八进制。如果在运行时用作 UInt 值,则数字字面值必须遵循 UInt 的位宽度;但如果它们仅在编译时出现,则可以是任意正数。Reach 为处理 Int 和带符号的 FixedPoint 数字提供了抽象。Int 可以通过对 UInt 类型的值使用一元 + 和 - 运算符来定义。Reach 为定义带符号的 FixedPoint 数字提供语法糖,该语法糖以10为基数用十进制语法定义。
字符串字面值(又称字节字符串)可以写在双引号或单引号之间(不同样式之间没有区别),并使用与 JavaScript 相同的转义规则。
5.4.6.4.5 运算表达式
—
! a // 非- a // 减+ a // 加typeof avoid a
一元运算写作 UNAOP EXPRrhs ,其中 EXPR_rhs 是一个表达式,UNAOP 是几种一元运算之一: ! - + typeof void 。除 [typeof](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28typeof%29%29%29) 之外的所有一元运算,均在标准库中有相应的命名版本。
应用于 UInt 类型的值时,一元 - 运算符和 + 运算符将其参数强制转换为 Int 类型。一元 - 运算符和 + 运算符是为 Int 和 FixedPoint 类型的值定义的。
—
a && ba || ba + ba - ba * ba / ba % ba | ba & ba ^ ba << ba >> ba == ba != ba === ba !== ba > ba >= ba <= ba < b
并非所有共识网络都支持位运算,并且位运算大大降低了验证效率。
二元表达式写作 EXPRlhs BINOP EXPR_rhs ,其中 EXPR_lhs 和 EXPR_rhs 均为表达式,BINOP 是几种二元运算之一: && || + - * / % | & ^ << >> == != === !== > >= <= < 。运算符 [==](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28~3d~3d%29%29%29)(和 === )和 !=(和 !== )均可对所有原子值运算。数值运算符,如 + 和 > ,只能对数字运算。由于 Reach 中所有数字均是整数,如 / 的运算符会截取其运算结果。 布尔运算符,如 && ,只能对布尔值进行运算。对错误类型的值使用二元运算是无效的。
and(a, b) // &&or(a, b) // ||add(a, b) // +sub(a, b) // -mul(a, b) // *div(a, b) // /mod(a, b) // %lt(a, b) //
所有二元表达式运算都在标准库中有相应的命名函数。尽管 && 和 || 可能不会计算它们的第二个参数,但它们相应的命名函数 and 和 or 总是会计算。
polyEq(a, b) // 在所有类型上相等boolEq(a, b) // 布尔值相等typeEq(a, b) // 类型相等intEq(a, b) // UInt 相等digestEq(a, b) // 摘要相等addressEq(a, b) // 地址相等fxeq(a, b) // FixedPoint 相等ieq(a, b) // Int 相等
== 是一个在所有类型上运算的函数。两个参数必须类型相等。对于每个支持的类型,都有专门的函数用于相等性检查。
—
若 verifyArithmetic 为 true ,则算术运算会自动作出静态断言,即它们的参数不会溢出可用共识网络的位宽度。如果为 false ,则连接器将动态地确保这一点。
5.4.6.4.6 异或
xor(false, false); // falsexor(false, true); // truexor(true, false); // truexor(true, true); // false
xor(Bool, Bool) 只有在两个输入值不同时才会返回 true 。
5.4.6.4.7 带括号表达式
表达式可以用括号括起来,如 (EXPR) 。
5.4.6.4.8 元组
元组字面写作 [ EXPR_0, …, EXPR_n ] ,它是能计算出一个带有 n 个值的元组的表达式,其中 EXPR_0 到 EXPR_n 都是表达式。
..expr 有可能出现在元组表达式内部,在这种情况下,展开的表达式必须计算为元组或数组,该结果也是按照对应表达式位置拼接而成的。
5.4.6.4.9 数组
const x = array(UInt, [1, 2, 3]);
将特定类型的单一类型值 tuple 转化为一个数组。
5.4.6.4.10 元素引用
arr[3]
引用写作 REF_EXPR[IDX_EXPR] ,其中 REF_EXPR 是用于计算出一个数组、一个元组或是一个结构体的表达式。 IDX_EXPR 则是用于计算出一个小于数组长度的自然数的表达式。引用选择数组给定索引处的元素。下标从零开始。
若 REF_EXPR 是一个映射,且 IDX_EXPR 的计算结果是一个地址,那么这个引用的计算结果是类型为 Maybe(TYPE) 的值,其中 TYPE 是映射的类型。
5.4.6.4.11 数组和元组的长度:Tuple.length ,Array.length ,和 .length
Tuple.length(tup);tup.length;Array.length(arr);arr.length;
两者都可以缩写为 expr.length ,其中 expr 的计算结果为元组或数组。
5.4.6.4.12 数组和元组的更新:Tuple.set ,Array.set ,和 .set
Tuple.set(tup, idx, val);tup.set(idx, val);Array.set(arr, idx, val);arr.set(idx, val);
Tuple.set 返回一个与 tup 相同的新元组,只是索引 idx 替换为 val 。
Array.set 返回一个与 arr 相同的新数组,只是索引 idx 替换为 val 。
两者都可以缩写为 expr.set(idx, val) ,其中 expr 的计算结果为元组或数组。
5.4.6.4.13 可折叠操作
Foldable.forEach && .forEach
c.forEach(f)Foldable.forEach(c, f)Array.forEach(c, f)Map.forEach(c, f)
Foldable.forEach(c, f) 在容器 c 的元素上迭代函数 f ,并丢弃结果。这可以缩写为 c.forEach(f) 。
Foldable.all && .all
Foldable.all(c, f)Array.all(c, f)Map.all(c, f)c.all(f)
Foldable.all(c, f) 确定容器 c 里面的每个元素是否都符合要求 f 。
Foldable.any && .any
Foldable.any(c, f)Array.any(c, f)Map.any(c, f)c.any(f)
Foldable.any(c, f) 确定容器 c 中是否至少有一个元素符合要求 f 。
Foldable.or && .or
Foldable.or(c)Array.or(c)Map.or(c)c.or()
Foldable.and && .and
Foldable.and(c)Array.and(c)Map.and(c)c.and()
Foldable.and(c) 以布尔值返回容器的结合。
Foldable.includes && .includes
Foldable.includes(c, x)Array.includes(c, x)Map.includes(c, x)c.includes(x)
Foldable.includes(c, x) 确定容器是否包含元素 x 。
Foldable.count && .count
Foldable.count(c, f)Array.count(c, f)Map.count(c, f)c.count(f)
Foldable.count(c, f) 返回容器 c 中满足要求 f 的元素个数。
Foldable.size && .size
Foldable.size(c)Array.size(c)Map.size(c)c.size()
Foldable.size(c) 返回 c 中元素的个数。
Foldable.min && .min
Foldable.min(c)Array.min(c)Map.min(c)c.min()
Foldable.min(arr) 返回 UInt 容器中的最小值。
Foldable.max && .max
Foldable.max(c)Array.max(c)Map.max(c)c.max()
Foldable.max(c) 返回 UInt 容器中的最大值。
Foldable.sum && .sum
Foldable.sum(c)Array.sum(c)Map.sum(c)c.sum()
Foldable.sum(c) 返回 UInt 容器中的总和。
Foldable.product && .product
Foldable.product(c)Array.product(c)Map.product(c)c.product()
Foldable.product(c) 返回 UInt 容器中的乘积。
Foldable.average && .average
Foldable.average(c)Array.average(c)Map.average(c)c.average()
Foldable.average(c) 返回 UInt 容器中的平均数。
5.4.6.4.14 数组的组运算
Array.iota
Array.iota(5)
Array.iota(len) 返回一个长度为 len 的数组,其中每个元素都等于其索引值。例如,Array.iota(4) 返回 [0,1,2,3] 。给定的 len 在编译时必须计算为整数。
Array.replicate && .replicate
Array.replicate(5, “five”)Arrayreplicate(5, “five”)
[Array](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28.Array%29%29%29).replicate(len,val) 返回一个长度为 len 的数组,其中每个元素都是 val 。例如:Array.replicate(4,”four”) 返回 [“four”,”four”,”four”,”four”] 。给定的 len 在编译时必须计算为整数。
Array.concat && .concat
Array.concat(x, y)x.concat(y)
Array.concat(x, y) 将数组 x 和 y 拼接。这可以简写为 x.concat(y) 。
Array.empty
Arrayempty[Array](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28.Array%29%29%29).empty
Array.empty 是一个没有元素的数组。它是 Array.concat 的标识元素,也可以写成 Array_empty 。
Array.zip&&.zip
Array.zip(x, y)x.zip(y)
Array.zip(x,y) 返回一个长度与 x 和 y(x 和 y 长度必须相同)相同的新数组,该新数组的元素是由 x 和 y 的元素构成的元组。这可以简写成 x.zip(y) 。
Array.map && .map
Array.map(arr, f)arr.map(f)
Array.map(arr,f) 返回一个长度与 arr 相等的新数组 arrmapped ,其中对所有的 i 而言,arr_mapped[i] = f(arr[i]) 。例如:[Array](https://docs.reach.sh/ref-programs-compute.html#%28reach.%28%28.Array%29%29%29).iota(4).map(x => x + 1) 返回 [1, 2, 3, 4] 。这可以简写成 arr.map(f) 。
依此函数类推,可用于相同大小的任意数量的数组,这些数组要在 f 参数之前提供。例如:
Array.iota(4).map(Array.iota(4), add) 返回 [0,2,4,6] 。
Array.reduce && .reduce
Array.reduce(arr, z, f)arr.reduce(z, f)
Array.reduce(arr,z,f) 返回函数 f 在给定数组上的左折叠),其初始值为 z 。例如:
Array.iota(4).reduce(0,add) 返回 ((0+1)+2)+3=6 。
这可以简写成 arr.reduce(z,f) 。
依此函数类推,可用于相同大小的任意数量的数组,这些数组要在 z 参数之前提供。例如:
Array.iota(4).reduce(Array.iota(4), 0, (x, y, z) => (z + x + y)) 返回 ((((0 + 0 + 0) + 1 + 1) + 2 + 2) + 3 + 3) 。
Array.indexOf && .indexOf
Array.indexOf(arr, x)arr.indexOf(x)
Array.indexOf(arr,x) 返回给定数组中第一个等于 x 的元素的索引。返回值的类型为 Maybe(UInt) 。若数组内没有符合条件的元素,则返回 None 。
5.4.6.4.15 映射的组运算
映射( Map )是一个 Foldable 容器。映射可以通过以下操作和 while 循环的常量内进行 Foldable 操作而被聚合。
Map.reduce&&.reduce
Map.reduce(map, z, f)map.reduce(z, f)
Map.reduce(map,z,f) 返回函数 f 在给定映射上的左折叠,初始值为 z 。例如:m.reduce(0, add) 汇总计算映射中的元素。 这可以简写为 map.reduce(z, f) 。
函数 f 必须满足如下特性:对所有的 z, a, b 而言,f(f(z, b), a) == f(f(z, a), b) ,因为运算的顺序是不可预测的。
5.4.6.4.16 对象
{ }{ x: 3, “yo-yo”: 4 }{ [1 < 2 ? “one” : “two”]: 5 }
一个典型的对象格式为:{KEY_0: EXPR_0, …, KEY_n: EXPR_n} ,其中 KEY_0 到 KEY_n 是标识符或者字符串字面值;EXPR_0 到 EXPR_n 是表达式。对象也是一个表达式,用于计算出带有字段 KEY_0 到 KEY_n 的对象。
为方便起见,还存在其他对象字面语法,例如:
{ …obj, z: 5 }
是一个对象拼接,将 obj 中的所有字段复制到对象中;其后也可以在这些字段之外添加额外字段。
{ x, z: 5 }
是 {x: x, z: 5} 的缩写,其中 x 是任意绑定字符串。
5.4.6.4.17 结构体
const Posn = Struct([[“x”, UInt], [“y”, UInt]]);const p1 = Posn.fromObject({x: 1, y: 2});const p2 = Posn.fromTuple([1, 2]);
结构体是元组和对象的组合。它有命名的元素,与对象相似,但又像一个元组进行排序,所以它的元素可以通过名称或位置来访问。存在用于与非 Reach 远程对象连接的结构体,其中双方必须对值在运行时的表现形式达成一致。
可以通过调用结构体类型实例(如 Posn )的 fromTuple 方法,外加一个适当长度的元组,来构造结构体实例。
可以通过调用结构体类型实例(如 Posn )的 fromObject 方法,外加一个具有合适字段的对象,来构造结构体实例。
结构体可以通过 Struct 值(以及结构体类型实例,如上述例子中的 Posn )上的的 toTuple 和 toObject 方法转换为相应的元组或对象:
assert(Posn.toTuple(p1)[0] == 1);assert(Struct.toObject(p2).y == 2);
5.4.6.4.16 字段引用
obj.x
字段引用写作 OBJ.FIELD ,其中 OBJ 是计算出一个对象或结构体的表达式,FIELD 是一个有效标识符,用于访问 OBJ 对象的 FIELD 字段。
5.4.6.4.19 Object.set
Object.set(obj, fld, val);Object_set(obj, fld, val);{ …obj, [fld]: val };
返回一个与 obj 相同的新对象, 只是字段 fld 被替换为 val 。
5.4.6.4.20 Object.setIfUnset
Object.setIfUnset(obj, fld, val);Object_setIfUnset(obj, fld, val);
返回一个与 obj相同的新对象,但如果 obj 中不存在 fld ,则添加值为 val 的 字段 fld 。
5.4.6.4.21 Object.has
Object.has(obj, fld);
返回一个布尔值,表示对象是否包含字段 fld 。这是静态已知的。
5.4.6.4.22 数据
const Taste = Data({Salty: Null,
Spicy: Null,
Sweet: Null,
Umami: Null});
const burger = Taste.Umami();
const Shape = Data({ Circle: Object({r: UInt}),
Square: Object({s: UInt}),
Rect: Object({w: UInt, h: UInt}) });
const nice = Shape.Circle({r: 5});
数据实例写作 DATA.VARIANT(VALUE) ,其中 DATA 是 Data 类型,VARIANT 是 DATA 的变量中某一个的名称, VALUE 是匹配变量类型的值。作为一种特殊情况,当变量的类型为 Null 时,可以省略该值,如上文中 burger 的定义所示。
5.4.6.4.23 Maybe
const MayInt = Maybe(UInt);
const bidA = MayInt.Some(42);
const bidB = MayInt.None(null);
const getBid = (m) => fromMaybe(m, (() => 0), ((x) => x));
const bidSum = getBid(bidA) + getBid(bidB);
assert(bidSum == 42);
选项类型可通过内置的 Data 类型 Maybe 在 Reach 中表示,它有两个变量:Some 和 None 。
Maybe 是由 export const Maybe = (A) => Data({None: Null, Some: A}); 定义的。
因此它是一个函数,返回一个专用于 Some 变量中特定类型的 Data 类型。
Maybe 实例可以通过 fromMaybe(mValue, onNone, onSome) 很方便地被使用,其中 onNone 是无参数的函数,当 mValue 为 None 时 onNone 会被调用;onSome 是 on 参数的函数,当 mValue 为 Some 时 onSome 的值会被调用,mValue 是 Maybe 的数据实例。
const m = Maybe(UInt).Some(5);
isNone(m); // false
isSome(m); // true
isNone 是一种方便的方法,用于确定变量是否为 isNone 。
isSome 是一种方便的方法,用于确定变量是否为 isSome 。
5.4.6.4.24 Either
Either 通过 export const Either = (A, B) => Data({Left: A, Right: B}); 来定义
Either 用来代表带有两种可能类型的值。
与 Maybe 类似,Either 可以用来表示正确或错误的值。按照惯例,一个成功的结果存储在 Right 中。与 None 不同的是 ,Left 可携带有关错误的附加信息。
either(e, onLeft, onRight)
either(e, onLeft, onRight) 对于一个 Either 值 e 来说,Either 可以将函数 onLeft 或是 onRight 应用于相应的变量值。
const e = Either(UInt, Bool);
const l = e.Left(1);
const r = e.Right(true);
isLeft(l); // true
isRight(l); // false
const x = fromLeft(l, 0); // x = 1
const y = fromRight(l, false); // y = false
isLeft 是一种方便的方法,用于确定变量是否为 Left 。
isRight 是一种方便的方法,用于确定变量是否为 Right 。
fromLeft(e, default) 是一种方便的方法,该方法在变量为 Right 的时候返回 default ,否则返回 Left 中的值。
fromRight(e, default) 是一种方便的方法,该方法在变量为 Left 的时候返回 default ,否则返回 Right 中的值。
5.4.6.4.25 match
const Value = Data({
EBool: Bool,
EInt: UInt,
ENull: Null,
});
const v1 = Value.EBool(true);
const v2 = Value.EInt(200);
const isTruthy = (v) =>
v.match({
EBool: (b) => { return b },
EInt : (i) => { return i != 0 },
ENull: () => { return false }
});
assert(isTruthy(v1));
assert(isTruthy(v2));
match 表达式写作 VAR.match({ CASE … }) 。其中 VAR 是一个绑定到数据实例的变量,而 CASE 是 VARIANT: FUNCTION 形式。 CASE 中的 VARIANT 是变量或 default ,FUNCTION 是使用与变量构造函数相同参数的函数,如果变量的类型为 Null ,则没有参数。
match 和 switch 语句相似,但因为它是个表达式,所以用起来很方便,比如直接在赋值语句的右侧使用。
与 switch 语句类似,这些 case 应该是全面且非冗余的,所有 case 都具有空尾,并且只有在共识步骤时,才能在 case 中包含共识转移。
5.4.6.4.26 条件表达式
choosesFirst ? [ heap1 - amount, heap2 ] : [ heap1, heap2 - amount ]
条件表达式写作:CONDE [?](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28~3f%29%29%29) NOTFALSE_E : FALSE_E, 其中 COND_E ,NOT_FALSE_E ,和 FLASE_E 都是表达式。由 COND_E 的结果是否为 [false](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28false%29%29%29) 决定是用 NOT_FALSE_E 的值还是 FLASE_E 的值。
ite(choosesFirst, [heap1 - amount, heap2], [heap1, heap2 - amount])
条件表达式也可以用 ite 函数来表示。然而需要注意的是,该函数总是会执行所有分支,而常规条件表达式只会执行其中一个分支。
5.4.6.4.27 箭头表达式
(() => 4)
((x) => x + 1)
((x) => { const y = x + 1;
return y + 1; })
((x, y) => { assert(x + y == 3); })(1, 2);
((x, y) => { assert(x + y == 3); })(…[1, 2]);
((x, y = 2) => { assert(x + y == 3); })(1);
((x, y = 2) => { assert(x + y == 2); })(1, 1);
(([x, y]) => { assert(x + y == 3); })([1, 2]);
(({x, y}) => { assert(x + y == 3); })({x: 1, y: 2});
(([x, [y]]) => { assert(x + y == 3); })([1,[2]]);
(([x, {y}]) => { assert(x + y == 3); })([1,{ y: 2 }]);
箭头表达式写作: (LHS0, …, LHS_n) [=>](https://docs.reach.sh/ref-programs-compute.html?#%28reach.%28%28~3d._~3e%29%29%29) EXPR 。其中 LHS_0 到 LHS_n 是左侧,EXPR 是表达式,其计算结果是一个函数,该函数是 EXPR 在相应左侧兼容的n个值上的抽象。与函数定义一样,箭头表达式可以使用默认参数表示法。
5.4.6.4.28 makeEnum
const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
枚举(简称 enum )可以通过调用 makeEnum 函数来创建,如 makeEnum(N) 中所示,其中 N 是枚举中不同值的数目。这创建一个由 N+1个 值组成的元组,其中第一个值是 Fun([UInt], Bool) ,该值表明它的参数是否是枚举的值之一,接下来的 N 个值是不同的 UInt 。
5.4.6.4.29 assert
assert( claim, [msg] )
当有无效声明被生成时,Reach 编译器将生成反例(即,在程序中分配标识符以伪造 claim )。有可能写出一个 claim ,它实际上每次执行结果都为 true ,但我们目前的方法无法证明它的执行结果总是 true ;如果是这种情况,Reach 将无法编译程序,并报告其分析不完整。Reach 永远不会产生错误的反例。
它接受一个可选的字节参数,该参数会被包含在所有报告的违规中。
请参阅有关验证的导览章节,以更好地了解在程序中验证什么,以及如何进行验证。
5.4.6.4.31 possible
possible( claim, [msg] )
可能性断言只在基于诚实的前端和参与者的 claim 执行结果可能为 true时才有效。它接受一个可选的字节参数,该参数会被包含在所有报告的违规中。
5.4.6.4.32 digest
digest( arg_0, …, arg_n )
摘要对给定参数的二进制编码原语执行加密哈希。这将返回一个 Digest 值。采用的具体算法由连接器决定。
5.4.6.4.33 balance
balance();
balance(gil);
balance 原语返回 DApp 的合约帐户余额。它接受一个可选的非网络代币值,在这种情况下,它返回指定代币的余额。
5.4.6.4.34 lastConsensusTime
lastConsensusTime()
lastConensusTime 原语返回 DApp 最后一次发布的时间。若之前没有发布,例如在应用程序的开头 deployMode 为 ‘firstMsg’ 的时候,则此功能不可用。
为什么没有 thisConsensusTime ?一些网络只有在共识操作完成后才能观察其时间,再加上使用 thisConsensusTime 会增加完成共识操作的时间,因此不使用它有助于提高扩展性。
5.4.6.4.35 makeDeadline
const [ timeRemaining, keepGoing ] = makeDeadline(10);
makeDeadline(deadline) 将一个 UInt 作为参数,并返回一个可用于处理绝对截止日期的函数对。它在内部根据截止日期和最后共识时间来确定结束时间——当调用 makeDeadline.timeRemaining 时将计算结束时间和当前的最后共识时间之间的差值。keepGoing 确定当前的最后共识时间是否小于结束时间。对于 parallelReduce 表达式的 while 和 timeout 字段来说,这是非常典型的用法。例如:
const [ timeRemaining, keepGoing ] = makeDeadline(10);
const = [parallelReduce](https://docs.reach.sh/ref-programs-consensus.html#%28reach.%28%28parallel.Reduce%29%29%29)(…)
.invariant(…)
.while( keepGoing() )
.case(…)
.timeout( timeRemaining(), () => { … })
这种模式是如此常见,以至于可以简写成 .timeRemaining。
5.4.6.4.36 implies
implies( x, y )
若 x 为 false 或 y 为 true 则返回 true 。
5.4.6.4.37 ensure
ensure( pred, x )
制作一个 pred(x) 为 true 的静态断言,并返回 x 。
5.4.6.4.38 hasRandom
hasRandom
一种参与者交互接口,它将 random 指定为不带参数的函数,并返回位宽位的无符号整数。
5.4.6.4.39 compose
compose(f, g)
创建一个新函数并应用 g 作为其参数,然后将此结果传递给函数 f 。f 的参数类型必须是 g 的返回类型。
5.4.6.4.40 sqrt
sqrt(81, 10)
计算第一个参数的近似平方根。该方法利用巴比伦法计算平方根。第二个参数必须是 UInt ,其值在编译时是已知的,它表示算法应该执行的迭代次数。
作为参考,当执行 5 次迭代时,该算法可以可靠地计算出最大 32 的平方 或 1024 的平方根。当执行 10 次迭代时,该算法可以可靠地计算出高达 580 的平方或 336400 的平方根。
5.4.6.4.41 pow
pow (2, 40, 10) // => 1,099,511,627,776
pow(base,power,precision) 以幂次方的形式计算提升近似值。第三个参数必须是 UInt ,其值在编译时是已知的,它表示算法应该执行的迭代次数。作为参考,6次迭代提供了足够的精度来计算 2^64 - 1 ,因此它可以计算的最大幂是63。
5.4.6.4.42 带符号整数
标准库提供了处理带符号整数的抽象。以下定义用于表示 Int :
Int 被表示为一个对象,而不是一个标量值,因为某些 Reach 需要对接的平台不提供对带符号整数的本地支持。
const Int = { sign: bool, i: UInt };
const Pos = true;
const Neg = false;
int(Bool, UInt) 是定义 Int 记录的缩写。也可以使用一元运算符 + 和 - 来替代 UInt 声明整数。
int(Pos, 4); // 代表 4
int(Neg, 4); // 代表 -4
-4; // 代表 -4
+4; // 代表 4 : Int
4; // 代表 4 : UInt
iadd(x, y) 将 Int x 和 Int y 相加。
isub(x, y) 把 Int y 从 Int x 中减去。
imul(x, y) 将 Int x 和 Int y 相乘。
imod(x, y) 求 Int x 除以 Int y 的余数。
ilt(x, y) 确定 x 是否小于 y 。
ile(x, y) 确定 x 是否小于或等于 y 。
igt(x, y) 确定 x 是否大于 y 。
ige(x, y) 确定 x 是否大于或等于 y 。
ieq(x, y) 确定 x 是否等于 y 。
ine(x, y) 确定 x 是否不等于 y 。
imax(x, y) 返回两个 Int 中较大的。
abs(i) 返回一个 Int 的绝对值,该返回值的类型为 UInt 。
5.4.6.4.43 定点数
定点数 FixedPoint 由以下定义:
export const FixedPoint = Object({ sign: bool, i: Object({ scale: UInt, i: UInt }) });
FixedPoint 可用来表示小数点后有固定位数的数字。它们可以很方便地表示分数,特别是在以10为分母的情况下。定点数的值是由分母 i 除以其分子 scale 确定的。例如:我们可以用 {sign: Pos, i: {scale : 1000, i : 1234}} 或者 fx(1000)(Pos, 1234) 来表示值 1.234 。或者,Reach提供了定义 FixedPoint 数字的语法糖。可以简单地写一个数字 1.234 ,假设值是以 10 为基数。1000 的分子精确到小数点后3位。同样,100 的分子精确到小数点后2位。
const scale = 10;
const i = 56;
fx(scale)(Neg, i); // 代表 - 5.6
fx(scale)(i) 将返回一个函数,该函数可用于实例化具有特定分子的定点数。
const i = 4;
fxint(-i); // 代表 - 4.0
fxint(Int) 将参数 Int 转换为小数位数为1的定点数。
const x = fx(1000)(Pos, 1234); // x = 1.234
fxrescale(x, 100); // => 1.23
fxrescale(x, scale) x将一个定点数从一个精度转换成另一个精度。此操作可能导致精度丢失,如上面的示例所示。
const x = fx(1000)(Pos, 824345); // x = 824.345
const y = 45.67;
fxunify(x, y); // => [ 1000, 824.345, 45.670 ]
fxunify(x,y) 将两个定点数转换为相同精度。取两个参数中精度较高者作为精度标准。函数将返回一个三元素元组,由公用精度和新精度值组成。
fxadd(x, y) 将两个定点数相加。
fxsub(x, y) 将两个定点数相减。
fxmul(x, y) 将两个定点数相乘。
fxdiv(34.56, 1.234, 10) // => 28
fxdiv(34.56, 1.234, 100000) // => 28.0064
fxdiv(x, y, scale_factor) 将两个定点数相除。分子 x 将乘以精度 scale_factor 以获得更精确的答案。如上所示。
fxmod(x, y) 求 x 除以 y 的余数。
fxfloor(x) 返回不超过 x 的最大整数。
fxsqrt(x, k) 估算定点数 x 的开方值, 以 k 作为 sqrt 算法的迭代次数。
const base = 2.0;
const power = 0.33;
fxpow(base, power, 10, 1000);// 1.260
fxpow(base, power, 10, 10000);// 1.2599
fxpow(base, power, 10, 1000000);// 1.259921
fxpow(base, power, precision, scalePrecision) 估算特定数 base 的 power 次幂。第三个参数必须是 UInt ,其值在编译时是已知的,它表示算法应该执行的迭代次数。scalePrecision 参数必须是 UInt 并表示返回值的精度。选择更大的 scalePrecision 可以在估算幂次值时获得更高的精度,如上所示。
fxpowi(base, power, precision) 估算特定数 base 的 power 次幂,power 为 UInt 。 第三个参数必须是 UInt ,其值在编译时是已知的,它表示算法应该执行的迭代次数。例如:6 次迭代为计算最多 2^64 - 1 提供足够的精度,因此幂指数最多能计算到 63 。
fxpowui(5.8, 3, 10);// 195.112
fxpowui(base, power, precision) 估算特定数 base 的 power 次幂,power 为 UInt 。第三个参数必须是 UInt ,其值在编译时是已知的。
fxcmp(op, x, y) 在统一两个定点数的精度之后,将比较运算符应用于这两个定点数。
有一些简便方法可用于比较定点数:
fxlt(x, y) 测试 x 是否小于 y 。
fxle(x, y) 测试 x 是否小于等于 y 。
fxgt(x, y) 测试 x 是否大于 y 。
fxge(x, y) 测试 x 是否大于等于 y 。
fxeq(x, y) 测试 x 是否等于 y 。
fxne(x, y) 测试 x 是否不等于 y 。
5.4.6.4.44 Anybody
Anybody.publish(); // race(…Participants).publish()
Reach 提供了一个缩写 Anybody ,它是所有参与者之间 race 的缩写。这种缩写在谁 publish 无关紧要的情况下非常有用,例如在 timeout 的情况下。
Anybody 严格限定是一个缩写,该缩写处理所有指定参与者申请的 race 。在具有参与者类的应用程序中,这意味着任何主体,因为不限制哪些主体(即地址)可以作为该类的成员。在没有任何参与者类的应用程序中,Anybody 只表示实际的先前绑定的参与者。
5.4.6.4.45 ’use strict’
‘use strict’;
‘use strict’ 允许所有后续声明启用未使用的变量在当前范围内进行检查。如果声明了一个变量,但从未使用过,则编译时将发出一个错误。
strict 模式将拒绝一些通常有效的代码,并限制 Reach 的类型系统如何动态执行。例如,Reach 通常允许计算以下表达式:
const foo = (o) =>
o ? o.b : false;
void foo({ b: true });
void foo(false);
Reach 允许 o 是带有 b 字段的对象或 false ,因为它在编译时只执行一部分程序。因此,如果没有 ‘use strict’ ,当 o = false 时,Reach 将不会计算 o.b ,并且此代码将成功编译。
但是,在 strict 模式下,Reach 将确保此程序将 o 视为具有单一类型,并检测程序中的错误,如下所示:
reachc: error: Invalid field access. Expected object, got: Bool
在 strict 模式下编写这样的程序的正确方法,是使用 Maybe 。就像这样:
const MObj = Maybe(Object({ b : Bool }));
const foo = (mo) =>
mo.match({
None: (() => false),
Some: ((o) => o.b)
});
void foo(MObj.Some({ b : true }));
void foo(MObj.None());
5.4.6.4.46 Intervals
Interval 由:export const Interval = Tuple(IntervalType, Int, Int, IntervalType); 定义。其中 IntervalType 由以下代码定义:
export const [ isIntervalType, Closed, Open ] = mkEnum(2);
export const IntervalType = Refine(UInt, isIntervalType);
构造器
区间可以用元组表示法或函数来构造:
// 代表 [-10, +10)
const i1 = [Closed, -10, +10, Open];
const i2 = interval(Closed, -10, +10, Open);
const i3 = intervalCO(-10, +10);
为方便起见,Reach提供了许多构造区间的功能:
interval(IntervalType, Int, Int, IntervalType) 构造一个区间,其中第一个和第二个参数表示左端点,以及它是开放还是闭合的;第三个和第四个参数表示右端点以及它是开放还是闭合的。
intervalCC(l, r) 构造两边端点为 Int 类型的闭合区间。
intervalCO(l, r) 构造一个半开放区间,该区间是两边端点为 Int 类型的左闭右开区间。
intervalOC(l, r) 构造一个半开放区间,该区间是两边端点为 Int 类型的左开右闭区间。
intervalOO(l, r) 构造两边端点为 Int 类型的开放区间。
访问器
leftEndpoint(i) 以 Int 类型返回区间的左端点。
rightEndpoint(i) 以 Int 类型返回区间的右端点。
关系运算
区间可以下列函数进行比较:
intervalEq(l, r) 测试区间是否相等。
intervalNe(l, r) 测试区间是否不相等。
intervalLt(l, r) 测试左边的区间是否小于右边的区间。
intervalLte(l, r) 测试左边的区间是否小于等于右边的区间。
intervalGt(l, r) 测试左边的区间是否大于右边的区间。
intervalGte(l, r) 测试左边的区间是否大于等于右边的区间。
算术运算
intervalAdd(l, r) 将两个区间相加。
intervalSub(l, r) 将两个区间相减。
intervalMul(l, r) 将两个区间相乘。
intervalDiv(l, r) 将两个区间相除。
其他运算
const i1 = intervalOO(+3, +11); // (+3, +11)
const i2 = intervalCC(+7, +9); // [+7, +9]
intervalIntersection(i1, i2); // [+7, +11)
intervalIntersection(x, y) 返回两个区间的交集。
const i1 = intervalOO(+3, +9); // (+3, +9)
const i2 = intervalCC(+7, +11); // [+7, +11]
intervalUnion(i1, i2); // (+3, +11]
intervalUnion(x, y) 返回两个区间的并集。
intervalWidth(intervalCC(+4, +45)); // +41
intervalWidth(i) 返回区间的宽度。
intervalAbs(intervalCC(+1, +10)); // +10
intervalAbs(i) 返回区间的绝对值。