Skip to content

表达式求值器使用教程

hsiaofongw edited this page Apr 1, 2022 · 26 revisions

使用教程

欢迎使用表达式求值器,它支持对算术表达式求值,以及进行简单的编程操作,但尚不支持复杂的编程操作。

让我们开始吧:

算术表达式

最基础地,求值器支持对加减乘除运算表达式求值,按照 README 上说的构建项目之后,执行 node dist/main repl 进入 REPL 环境,然后我们可以对表达式求值:

请你分别尝试:

1 + 1;
1 + 2 * 3 / 4;
3 + 4 + -2 + 5 % 3;

分号 ; 是分隔开各个表达式的符号,分号亦代表当前表达式的输入结束,输入分号后,再敲一次回车键,即可触发操作系统刷新缓冲区,促进输入内容在求值管道中的流转。

乘法运算符和乘法运算符具有更高的结合优先级,而单个(前缀)取负符号比取余数符号 % 的优先级更高,取余数符号 % 的优先级比乘除号的优先级高。

我们也可以通过括号来强制优先级:

例如,对 3 + 4 * 5;(3 + 4) * 5; 求值将得到不一样的结果。

条件求值与布尔运算

And, OrNot 符号提供了基本的布尔值运算功能,<, <=, >, >=, ==, !== 提供了数和数之间的比较功能,If 提供了条件求值功能。

If[True, a, b]; 的求值结果为 a;

试一试下面这一段程序,它就利用了条件求值功能来寻找一个非空列表的最大值:

myList = RandomInteger[{1, 20}, 4];

Let[
  max[lst_] := If[
    Length[lst] == 0,
    Nothing,
    Reduce[
      lst,
      Function[x_, y_, If[y > x, y, x]],
      First[lst]
    ]
  ],
  max[myList]
];

函数

普通函数

您无需理解系统内部是如何实现的,只需知道:

f[x_, y_] := x + y;

这条语句定义了一个函数,基本语法是

函数名标识符[变量a_, 变量b_(, ...)] := 表达式
g[x_, y_] := f[x+y, x]

也是一个函数,这样定义的函数我们可以简单地称之为「普通函数」。

匿名函数

有时,我们更关心函数的计算方式而不是函数本身的名字,

举例:

Function[x_, y_, z_, x + y + z][1,2,3];

是匿名函数的一个应用(Application),其中 Function[x_, y_, z_, x + y + z] 定义了一个三个参数的匿名函数,它的输出结果是这三个参数的算术和,而后紧接着 [1, 2, 3] 表示对这个匿名函数的一个「调用」(Apply)操作。

如果我们需要描述一个计算法则,但是不想给这个计算法则起一个专门的名字的话,匿名函数是有用的。

下列表达式定义了一个匿名函数,它的作用是让调用者传入的参数自己与自己相乘并返回结果:

Function[x_, x * x]

你可以定义匿名函数后立即开始计算:

Function[x_, x * x][12];

也可以将匿名函数与一个标识符「绑定」在一起,然后通过这个标识符来调用这个函数:

squareFn = Function[x_, x * x];
squareFn[101];

匿名函数也可以作为函数参数,例如,将一个 C 语言风格的 compare 函数传入排序函数中作为比较器:

mySort[lst_, compareFn_] := If[
  Length[lst] == 0, 
  {},
  Let[
    first = First[lst],
    rest = Rest[lst],
    lhs = Filter[rest, Function[x_, compareFn[x, first] <= 0]],
    rhs = Filter[rest, Function[x_, compareFn[x, first] > 0]],
    ListJoin[mySort[lhs, compareFn], { First[lst] }, mySort[rhs, compareFn]]
  ]
];

mySort[RandomInteger[{1,1000}, 40], Function[x_, y_, y - x]];

在这里,由于我们传入 mySort 函数的比较器是 Function[x_, y_, y - x], 所以,最终的排序输出结果会是倒序排列的,也就是说大的数排在前面,小的数排在后面。

递归函数

系统当前的实现支持递归函数,递归函数就是在函数体表达式中出现了对自己的调用的表达式。你可以通过精心定义一个递归函数,来实现传统指令式编程 (Imperative Programming) 中的控制流。

下面我们定义一个求和函数,它的功能是计算前 10 个正整数的和,并且,定义好之后就调用它:

sum[n_] := Let[
  sumIter[sum_, count_, max_] := If[count > max, sum, sumIter[sum + count, count + 1, max]],
  sumIter[0, 0, n]
];

sum[10];

你也可以将以上内容保存为一个后缀名为 .wl 的脚本文件中,例如 sumIter.wl 或者 sum.wl, 然后通过运行

node dist/main run sum.wl

来执行这个脚本。

我们还可以定义斐波那契函数:

Let[
  fib[1] = 1,
  fib[2] = 1,
  fib[n_] := fib[n-1] + fib[n-2],
  fib[7]
]

Let 符号用来创建临时作用域,因此在 Let 中给出的定义在 Let 表达式求值完毕后就会被系统丢弃,但是像刚才的 sum 函数,由于是定义在 Let 外边的,所以是永久的,而 Let 里边的 sumIter 则是临时的。

输入下列表达式可以定义一个排序函数:

sort[lst_] := If[
  Length[lst] == 0,
  {},
  Let[
    first = First[lst],
    rest = Rest[lst],
    ListJoin[
      sort[Filter[rest, Function[x_, x <= first]]],
      { first },
      sort[Filter[rest, Function[x_, x > first]]]
    ]
  ]
];

通过 sort[{3,18,9,2,6}]; 来调用它,可以看到列表升序排列后的结果。

列表以及列表上的操作

构造序列

Seq 函数可以用来创建包含等量递增的整数的列表:

Seq[10];
Seq[2, 6];
Seq[1, 10, 2];
Seq[-10, 10];
Seq[-10, 10, 4];

请逐个尝试上列表达式。

RandomInteger 可以用来创建包含随机整数的列表:

RandomInteger[{ 3, 4 }, 5];

返回的列表中的整数的值是在一个闭区间里面随机取的,其中 3 是闭区间的下确界,4 是闭区间的上确界,如果用户输入的下确界大于上确界,则系统会交互这两个参数的角色,原来的上确界变为下确界,原来的下确界变为上确界,5 是产生的随机数的列表的长度,所以这条表达式返回 5 个随机整数,取值在 [3, 4].

批量操作

Map 函数可以用来实现对数组元素的批量操作:

Map[{a, b, c}, f];

上列表达式中的 a, b, c 没有定义也没关系,求值器遇到无定义的符号时会原样返回,例如 a 这个符号没有定义,那么当我们输入 a; 得到的返回还是 a.

上列表达式的返回结果是 { f[a], f[b], f[c] }, 也就是对列表中的每个元素「应用」了 f 操作后得到的运算结果(保持原有顺序)。

列表折叠

Reduce 函数的功能类似于「折叠」,它可以用来「折叠」一个列表:

Reduce[{a, b, c}, f, init];  (* 等于 f[f[f[init, a], b], c] *)

我们的自测例程就用到了 Reduce, 当你要对多个布尔表达式做 And(或者 Or)运算的时候:

举几个例子:

Reduce[{ True, True, False }, And, True]  (* 等于 False *)
Reduce[{ True, True, True, True }, And, True] (* 等于 True *)
Reduce[{ False, True, False, False }, Or, False] (* 等于 True *)

道理跟 Reduce 的定义是基本一样的。

Reduce 还能用来求和:

Reduce[Seq[10], Function[x_, y_, x + y * y], 0];  (* 平方和 *)
Reduce[Map[Seq[10], Function[x_, x * x]], Plus, 0];  (* 平方和,同上 *)
Reduce[Seq[10], Function[x_, y_, x + y * y * y], 0];  (* 立方和 *)

条件筛选

Filter 可以用来过滤数组的元素,留下那些符合条件的,例如:

筛选出 Seq[10] 中的奇数:

Filter[Seq[10], Function[x_, x % 2 == 1]];

筛选出 Seq[10] 中的偶数:

Filter[Seq[10], Function[x_, x % 2 == 0]];

下列程序筛出 100 以内的素数:

primeFilter[lst_] := If[
  Length[lst] == 0,
  {},
  Let[
    first = First[lst],
    rest = Rest[lst],
    ListJoin[
      { first },
      primeFilter[
        Filter[
          rest, 
          Function[x_, x % first !== 0]
        ]
      ]
    ]
  ]
];

primes[n_] := If[n < 2, {}, primeFilter[Seq[2, n]]];
primes[100];

变量与赋值

全局作用域下的赋值操作

在我们实现的表达式求值器中,没有变量,只有替换规则和表达式,Pattern 本身也是用表达式表示的。

a = RandomInteger[{ 1, 10 }];

这条语句会被翻译为:

Assign[a, RandomInteger[{ 1, 10 }]];

系统看到该表达式的 Head 是 Assign, Assign 这个符号被标记为了「非标准求值符号」,于是系统进入非标准求值程序,系统中有一条针对 Assign[_, _] 表达式的求值规则:先求值右边的值,也就是 RandomInteger[{ 1, 10 }], 再将得到的结果和左边的值也就是 a 做关联,将这个绑定关系「a -> 这次对 RandomInteger[{ 1, 10 }] 的求值结果」加入替换规则库,下一次我们再输入 a; 就可以得到 a 这个 Pattern 绑定的值。

b := RandomInteger[{ 1, 10 }];

这条语句会被翻译为 AssignDelayed[b, RandomInteger[{ 1, 10 }]], 系统会简单地将 b 这个 Pattern 与 RandomInteger[{ 1, 10 }] 关联,所以之后我们每一次对 b 这个表达式求值,系统都会执行一次 RandomInteger[{ 1, 10 }]

f[x_] := RandomInteger[{ 1, 10 }];

会被翻译为 AssignDelayed[f[Pattern[x, Blank[]]], RandomInteger[{ 1, 10 }]], 系统在求值这条表达式的时候系统会在替换规则库中加入一条规则:

f[Pattern[x, Blank[]]] -> RandomInteger[{ 1, 10 }]]

下次加入我们尝试对 f[3] 或者 f[a] 或者 f[x] 求值都会被 f[Pattern[x, Blank[]]] 这个 Pattern 匹配,系统紧接着就会对替换规则的右端也就是 RandomInteger[{ 1, 10 }]] 求值。注意:f[] 不会被 f[Pattern[x, Blank[]]] 匹配。

局部(或临时)作用域下的赋值操作

Let 符号提供了临时局部作用域的使用,在 Let 表达式中,只有最后一个子表达式会被真正求值,第 1 个到第 n-1 个子表达式的 Assign 或者 AssignDelayed 表达式不会被求值而是会被添加到一个临时作用域中。

举例:

Let[a = 1, b = 2, f[x_, y_] := x + y, f[a, b]];

返回 3, 也就是 f[a, b], 也就是 a + b, 也就是 1 + 2, 对 f[a, b] 及其展开后的表达式的求值都是在这个 Let 创建的局部作用域中进行的。

Let 表达式求值完毕后,我们再尝试求值 a; 或者 b; 或者 f[a, b]; 都会发现没有定义(或者是 Let 之前的定义),如果没有定义系统会原样返回它们。

Let 表达式的 Assign(或者 AssignDelay)子表达式是逐级生效的,第 n-1 个先生效,然后是第 n 个,如下表达式:

Let[a = 1, b = a + 1, c = b + 1, c + 1];

的求值结果是 4.

下列程序定义了一系列局部变量用来演示快速排序的实现:

Let[
  sort[lst_] := If[
    Length[lst] == 0,
    {},
    Let[
      first = First[lst],
      rest = Rest[lst],
      lhs = Filter[rest, Function[x_, x <= first]],
      rhs = Filter[rest, Function[x_, x > first]],
      sortedLhs = sort[lhs],
      sortedRhs = sort[rhs],
      middle = { first },
      result = ListJoin[sortedLhs, middle, sortedRhs],
      result
    ]
  ],
  sort[RandomInteger[{1, 1000}, 40]]
];