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 ]
条件表达式写作:COND_E ? NOT_FALSE_E : FALSE_E, 其中 COND_E ,NOT_FALSE_E ,和 FLASE_E 都是表达式。由 COND_E 的结果是否为 false 决定是用 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 }]);
箭头表达式写作: (LHS_0, ..., LHS_n) => 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(...) .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) 返回区间的绝对值。