模块包

N.B. “Module” is an overloaded term in Raku; this document focuses on use of the module declarator.

注意 “模块”是 Raku 中的重载术语; 本文档重点介绍 module 声明符的使用。

什么是模块?

模块,如类和 grammars,是一种。模块对象是 ModuleHOW 元类的实例; 这提供了某些功能,可用于创建命名空间,版本控制,代理和数据封装(另请参见角色)。

要创建模块,请使用 module 声明符:

module M {}
say M.HOW;   # OUTPUT: «Raku::Metamodel::ModuleHOW.new» 

这里我们定义一个名为 M 的新模块; 内省 HOW 确认了底层的元类 MRaku::Metamodel::ModuleHOW

何时使用模块

模块主要用于封装不属于类或角色定义的代码和数据。模块内容(类,子程序,变量等)可以从具有 is export trait 的模块中导出; 一旦importuse 了模块,这些内容在调用者的命名空间中就可用了。模块还可以选择性地在其命名空间中通过 our 暴露符号以进行限定引用。

使用模块

为了说明模块作用域和导出规则,我们首先定义一个简单的模块 M

module M {
  sub greeting ($name = 'Camelia') { "Greetings, $name!" }
  our sub loud-greeting (--> Str)  { greeting().uc       }
  sub friendly-greeting is export  { greeting('friend')  }
}

回想一下,子例程是词法作用域的,除非另有说明(声明符 sub 等效于 my sub),因此greeting 在上面的示例中,词法作用域为模块并且在其外部不可访问。我们还使用 our 声明符定义了 loud-greeting,这意味着除了在词法作用域内,它还在模块的符号表中起了别名。最后,friendly-greeting 标记为导出; 导入模块时,它将在调用者的符号表中注册:

import M;               # import the module 
say M::loud-greeting;   # OUTPUT: «GREETINGS, CAMELIA!» 
say friendly-greeting;  # OUTPUT: «Greetings, friend!» 

磁盘上的模块

虽然 .pm.pm6 文件(以下简称: .pm6) 有时被称为“模块”,但它们实际上只是在您写了 needuse 或者 require 时加载和编译的普通文件。

对于我们一直使用的意义上提供模块的 .pm6 文件,它需要如上所述用的用 module 声明一个模块。例如,通过将模块 M 放入 Foo.pm6 内部,我们可以按如下方式加载和使用模块:

use Foo;                # find Foo.pm6, run need followed by import 
say M::loud-greeting;   # OUTPUT: «GREETINGS, CAMELIA!» 
say friendly-greeting;  # OUTPUT: «Greetings, friend!» 

注意文件名和模块名之间的解耦 - .pm6 文件可以声明零个或多个具有任意标识符的模块。

文件和模块命名

我们通常希望 .pm6 文件提供单个模块,仅此而已。这里的常见约定是文件 basename 与模块名称匹配。回到 Foo.pm6,显而易见的是,它仅提供单个模块,M; 在这种情况下,我们可能想要重命名 MFoo。修改后的文件将为:

module Foo {
  sub greeting ($name = 'Camelia') { "Greetings, $name!" }
  our sub loud-greeting (--> Str)  { greeting().uc       }
  sub friendly-greeting is export  { greeting('friend')  }
}

可被调用者更一致地使用(注意 use FooFoo:: 之间的关系):

use Foo;
say Foo::loud-greeting;  # OUTPUT: «GREETINGS, CAMELIA!» 
say friendly-greeting;   # OUTPUT: «Greetings, friend!» 

如果 Foo.pm6 在源树中放置得更深,例如在 lib/Utils/Foo.pm6 中,我们可以选择命名模块 Utils::Foo 以保持一致性。

unit 关键字

只提供单个模块的文件可以用 unit 关键字更简洁地编写; unit module 指定编译单元的其余部分是声明的模块的一部分。这里 Foo.pm6 使用 unit 重写:

unit module Foo;
 
sub greeting ($name = 'Camelia') { "Greetings, $name!" }
our sub loud-greeting (--> Str)  { greeting().uc       }
sub friendly-greeting is export  { greeting('friend')  }

单元声明后的所有内容都是 Foo 模块规范的一部分。

(请注意,unit 也可以用于 classgrammarrole)。

如果我省略了module会发生什么?

为了更好地理解在 Foo.pm6module 声明符在做什么,让我们将它与变体文件 Bar.pm6 进行对比,它省略了声明。下面的子程序定义几乎相同(唯一的区别在于 greeting 的正文,为了清晰起见而修改):

sub greeting ($name = 'Camelia') { "Greetings from Bar, $name!" }
our sub loud-greeting (--> Str)  { greeting().uc                }
sub friendly-greeting is export  { greeting('friend')           }

提醒一下,这是我们以前使用 Foo.pm6 的方式,

use Foo;
say Foo::loud-greeting;  # OUTPUT: «GREETINGS, CAMELIA!» 
say friendly-greeting;   # OUTPUT: «Greetings, friend!» 

这是我们使用 Bar.pm6 的方式,

use Bar;
say loud-greeting;       # OUTPUT: «GREETINGS FROM BAR, CAMELIA!» 
say friendly-greeting;   # OUTPUT: «Greetings from Bar, friend!» 

注意 loud-greeting 的使用,而不是 Bar::loud-greeting 因为 Bar 不是已知符号(我们没有在 Bar.pm6 中创建一个以那个名字命名的 module)。但是为什么 loud-greeting是可调用的, 即使我们没有将其标记为导出。答案很简单,Bar.pm6 不创建一个新的包命名空间 - $?PACKAGE 仍设置为 GLOBAL 当我们将 loud-greeting声明为 our 时,它被注册到 GLOBAL 符号表中。

词法别名和安全

值得庆幸的是,Raku 保护我们免受意外调用地点定义的痛击(例如内置函数)。除了 Bar.pm6 考虑以下内容:

our sub say ($ignored) { print "oh dear\n" }

这会创建一个词法别名,将内置 say 隐藏在 Bar.pm6 内部 但保持调用者 say 不变。因此,以下 say 调用仍然按预期工作:

use Bar;
say 'Carry on, carry on...';  # OUTPUT: «Carry on, carry on...»