5.4.6.4.24~46 表达式(下)

296 阅读13分钟
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 ,则没有参数。

matchswitch 语句相似,但因为它是个表达式,所以用起来很方便,比如直接在赋值语句的右侧使用。

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] )

静态断言只在 claim 恒为 true有效

当有无效声明被生成时,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 相乘。

idiv(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) 返回区间的绝对值。