函数是组织代码的一种有效形式,模块是比函数更大粒度的一个方式,模块将一系列相关的函数组织在一起,例如内置的 math 模块,就包含了一系列数学函数调用,

Screen Shot 2020-04-05 at 15.33.30.png

除了内置模块之外,你也可以将你的代码组织成模块的形式。

注意:模块系统需要启用 语法**Feature.Module**

exports

比如我们定义了一个快排的模块:

  1. ## examples/qsort.av
  2. ## Author: dennis
  3. ## Desc: a script to define a quick-sort function qsort.
  4. fn swap(xs, l, r) {
  5. let temp = xs[l];
  6. xs[l] = xs[r];
  7. xs[r] = temp;
  8. }
  9. fn partition(xs, l, r, p) {
  10. while l <= r {
  11. while xs[l] < p {
  12. l = l+1;
  13. }
  14. while xs[r] > p {
  15. r = r -1;
  16. }
  17. if l <= r {
  18. swap(xs, l, r);
  19. l = l+1;
  20. r = r -1;
  21. }
  22. }
  23. return l;
  24. }
  25. fn qsort0(xs, l, r) {
  26. if l >= r {
  27. return;
  28. }
  29. let p = xs[(l+r)/2];
  30. let i = partition(xs, l, r, p);
  31. qsort0(xs, l, i-1);
  32. qsort0(xs, i, r);
  33. }
  34. qsort = lambda(xs) ->
  35. qsort0(xs, 0, count(xs)-1);
  36. return xs;
  37. end;
  38. ## export qsort
  39. exports.sort = qsort;

这是一个经典的快速排序实现, swap 函数用于交换集合两个位置的元素, partition 用于划分集合, qsort0 分别对集合的左右两部分做递归排序,最终的 qsort 函数接受一个集合 xs ,然后调用 qsort0 进行实际的排序工作。

这里特别要注意的是 exports.sort = qsort 这一行,这样就将 qsort 函数设置为公开并命名为 sort ,模块外的代码可以访问 sort 方法,它将调用 qsort 函数。没有通过 **exports** 导出的函数,将无法被模块外的代码访问。

require 和 load

定义并导出了 qsort 模块后,我们可以在模块之外这么使用:

  1. ## examples/test_qsort.av
  2. let q = require('examples/qsort.av');
  3. fn rand_list(n) {
  4. let a = seq.list();
  5. for i in range(0, n) {
  6. seq.add(a, rand(n));
  7. }
  8. return a;
  9. }
  10. let a = rand_list(20);
  11. println("before sorting: " + a);
  12. q.sort(a);
  13. println("after sorting: " + a);

我们通过 let q = require('examples/qsort.av'); 引用了模块 qsort ,接下来调用 q.sort(a) 排序随机数组 a。

使用 RunScriptExample 来跑上述例子,输出:

  1. before sorting: [8, 12, 5, 13, 19, 3, 3, 6, 19, 18, 16, 18, 8, 13, 18, 6, 18, 2, 9, 13]
  2. after sorting: [2, 3, 3, 5, 6, 6, 8, 8, 9, 12, 13, 13, 13, 16, 18, 18, 18, 18, 19, 19]

require(path) 用于加载 path 路径指定的模块,查找的顺序跟 AviatorEvaluatorInstance#compileScript 一样:

  1. 先假设 path 是绝对路径,尝试加载
  2. 尝试从当前线程的类加载器 ClassLoader 使用 getResourceAsStream(path) 加载,如果未找到,则使用给 path 加上 / 前缀再次使用 getResourceAsStream 尝试查找。
  3. 最后,尝试从当前 AviatorEvaluatorInstance 类加载器,按照第二条规则类似的方式再次查找。
  4. 如果都没有找到,将抛出运行时异常。

require 函数如果加载成功,将返回模块的 exports ,这样就可以调用模块的导出函数了。

除了 require 函数之外,你也可以用 load(path函数,不同的是 load 每次都将重新编译并执行模块代码,而 require 是按照 path 做了模块缓存。假设我们修改 qsort 模块,加入一行打印:

  1. ## examples/qsort.av
  2. ## Author: dennis
  3. ## Desc: a script to define a quick-sort function qsort.
  4. fn swap(xs, l, r) {
  5. let temp = xs[l];
  6. xs[l] = xs[r];
  7. xs[r] = temp;
  8. }
  9. fn partition(xs, l, r, p) {
  10. while l <= r {
  11. while xs[l] < p {
  12. l = l+1;
  13. }
  14. while xs[r] > p {
  15. r = r -1;
  16. }
  17. if l <= r {
  18. swap(xs, l, r);
  19. l = l+1;
  20. r = r -1;
  21. }
  22. }
  23. return l;
  24. }
  25. fn qsort0(xs, l, r) {
  26. if l >= r {
  27. return;
  28. }
  29. let p = xs[(l+r)/2];
  30. let i = partition(xs, l, r, p);
  31. qsort0(xs, l, i-1);
  32. qsort0(xs, i, r);
  33. }
  34. qsort = lambda(xs) ->
  35. qsort0(xs, 0, count(xs)-1);
  36. return xs;
  37. end;
  38. ## export qsort
  39. exports.sort = qsort;
  40. ## println something
  41. println("load qsort module.");

我们继续用 require 来调用,只是重复两次:

  1. ## examples/test_qsort.av
  2. let q = require('examples/qsort.av');
  3. let q = require('examples/qsort.av');
  4. fn rand_list(n) {
  5. let a = seq.list();
  6. for i in range(0, n) {
  7. seq.add(a, rand(n));
  8. }
  9. return a;
  10. }
  11. let a = rand_list(20);
  12. println("before sorting: " + a);
  13. q.sort(a);
  14. println("after sorting: " + a);

这样将打印:

  1. load qsort module.
  2. before sorting: [4, 19, 2, 15, 4, 9, 3, 1, 19, 7, 11, 2, 8, 4, 17, 1, 7, 14, 11, 14]
  3. after sorting: [1, 1, 2, 2, 3, 4, 4, 4, 7, 7, 8, 9, 11, 11, 14, 14, 15, 17, 19, 19]

尽管我们调用了 require 两次,也只打印了一次 load qsort module. ,这就是 require 带了 module cache 的缘故。

如果我们将 require 替换为 load

  1. ## examples/load_qsort.av
  2. let q = load('examples/qsort.av');
  3. let q = load('examples/qsort.av');
  4. fn rand_list(n) {
  5. let a = seq.list();
  6. for i in range(0, n) {
  7. seq.add(a, rand(n));
  8. }
  9. return a;
  10. }
  11. let a = rand_list(20);
  12. println("before sorting: " + a);
  13. q.sort(a);
  14. println("after sorting: " + a);

这次将打印两次了:

  1. load qsort module.
  2. load qsort module.
  3. before sorting: [12, 19, 11, 0, 17, 13, 1, 3, 3, 0, 5, 18, 3, 13, 18, 15, 9, 6, 13, 3]
  4. after sorting: [0, 0, 1, 3, 3, 3, 3, 5, 6, 9, 11, 12, 13, 13, 13, 15, 17, 18, 18, 19]

通常来说你都应该使用 **require** 来减少模块不必要的重新编译和加载。

MODULE

在被加载模块中,可以通过 __MODULE__ 获取当前模块的信息,包括:

  • path ,当前模块的代码路径
  • exports ,当前模块导出的函数或者变量列表。

只有被 load 或者 require 的模块才有这 exports__MODULE__ 这两个特殊变量,其中 __MODULE__.exports 指向了 exports

因此可以直接赋值给 __MODULE__.exports 来整体导出一个方法列表:

  1. ## examples/module.av
  2. ## a test module
  3. let m = seq.map();
  4. m.add = lambda(x, y) ->
  5. x + y
  6. end;
  7. m.sub = lambda(x, y) ->
  8. x - y
  9. end;
  10. println("module path: " + __MODULE__.path);
  11. __MODULE__.exports = m;

我们创建了一个 map 实例 m ,并给 m 设置了两个变量 m.addm.sub 分别赋值了两个函数。最后将整个 m 赋值给了 __MODULE__.exports ,这样这个测试模块导出了两个方法 addsub ,我们可以这样使用它们:

  1. ## examples/test_module.av
  2. let t = require('examples/module.av');
  3. println(t.add(1, 2));
  4. println(t.sub(1, 2));

这样将打印:

  1. module path: examples/module.av
  2. 3
  3. -1

其中第一行是在 module.av 里打印的,而 3 和 -1 是分别执行 t.add(1, 2)t.sub(1, 2) 的结果。