注意 “模块” 在 Raku 中是一个重载的术语;本文档重点介绍 module
声明符的使用。
什么是模块?§
模块,就像类和语法一样,是一种 包。模块对象是 ModuleHOW
元类的实例;这提供了一些功能,这些功能对于创建命名空间、版本控制、委托和数据封装很有用(另请参见 类 和 角色)。
要创建模块,请使用 module
声明符
say M.HOW; # OUTPUT: «Perl6::Metamodel::ModuleHOW.new»
这里我们定义了一个名为 M
的新模块;使用 HOW
进行的内省确认 M
底层的元类是 Perl6::Metamodel::ModuleHOW
。
何时使用模块§
模块主要用于封装不属于类或角色定义的代码和数据。模块内容(类、子例程、变量等)可以使用 `is export` 特性从模块中导出;这些内容在模块使用 `import` 或 `use` 导入后,将在调用者的命名空间中可用。模块还可以通过 `our` 选择性地公开其命名空间内的符号,以便进行限定引用。
使用模块§
为了说明模块作用域和导出规则,让我们从定义一个简单的模块 `M` 开始
回想一下,子例程在词法上是作用域的,除非另有说明(声明符 sub
等同于 `my sub`),因此上面的例子中的 `greeting` 在词法上是作用域到模块的,在模块外部无法访问。我们还使用 `our` 声明符定义了 `loud-greeting`,这意味着除了词法作用域之外,它还在模块的符号表中被别名化。最后,`friendly-greeting` 被标记为导出;当模块被导入时,它将在调用者的符号表中注册。
import M; # import the modulesay M::loud-greeting; # OUTPUT: «GREETINGS, CAMELIA!»say friendly-greeting; # OUTPUT: «Greetings, friend!»
磁盘上的模块§
虽然 `.raku` 和 `.rakumod` 文件有时被称为“模块”,但它们实际上只是普通文件,当你写 `need`、`use` 或 `require` 时,它们会被加载和编译。
为了让 `.rakumod` 文件提供一个我们一直在使用的模块,它需要使用上面文档中的 `module` 声明一个模块。例如,通过将模块 `M` 放置在 `Foo.rakumod` 中,我们可以加载并使用该模块,如下所示
use Foo; # find Foo.rakumod, run need followed by importsay M::loud-greeting; # OUTPUT: «GREETINGS, CAMELIA!»say friendly-greeting; # OUTPUT: «Greetings, friend!»
注意文件和模块名称之间的解耦——一个 `.rakumod` 文件可以声明零个或多个具有任意标识符的模块。
文件和模块命名§
通常我们希望一个 `.rakumod` 文件提供一个单一的模块,而没有其他东西。这里一个常见的约定是让文件基名与模块名匹配。回到 `Foo.rakumod`,很明显它只提供了一个模块 `M`;在这种情况下,我们可能希望将 `M` 重命名为 `Foo`。修改后的文件将读取
这可以被调用者更一致地使用(注意 `use Foo` 和 `Foo::` 之间的关系)。
use Foo;say Foo::loud-greeting; # OUTPUT: «GREETINGS, CAMELIA!»say friendly-greeting; # OUTPUT: «Greetings, friend!»
如果 `Foo.rakumod` 被放置在源代码树的更深处,例如在 `lib/Utils/Foo.rakumod` 中,我们可以选择将模块命名为 `Utils::Foo` 以保持一致性。
`unit` 关键字§
只提供单个模块的文件可以使用 `unit` 关键字更简洁地编写;`unit module` 指定编译单元的其余部分是声明的模块的一部分。以下是使用 `unit` 重写的 `Foo.rakumod`
unit ;sub greeting ( = 'Camelia')our sub loud-greeting (--> Str)sub friendly-greeting is export
单元声明之后的所有内容都是 `Foo` 模块规范的一部分。
(注意 `unit` 也可以与 `class`、`grammar` 和 `role` 一起使用。)
如果我省略了 `module` 会发生什么?§
为了更好地理解 `module` 声明符在 `Foo.rakumod` 中的作用,让我们将其与一个省略了声明的变体文件 `Bar.rakumod` 进行对比。下面的子例程定义几乎相同(唯一的区别在于 `greeting` 的主体,为了清晰起见进行了修改)。
sub greeting ( = 'Camelia')our sub loud-greeting (--> Str)sub friendly-greeting is export
作为提醒,以下是我们之前如何使用 `Foo.rakumod` 的,
use Foo;say Foo::loud-greeting; # OUTPUT: «GREETINGS, CAMELIA!»say friendly-greeting; # OUTPUT: «Greetings, friend!»
以下是我们如何使用 `Bar.rakumod` 的,
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.rakumod` 中没有创建名为 `Bar` 的 `module`)。但是为什么 `loud-greeting` 可以调用,即使我们没有标记它为导出?答案很简单,`Bar.rakumod` 不会创建一个新的包命名空间——`$?PACKAGE` 仍然设置为 `GLOBAL`——因此当我们声明 `loud-greeting` 为 `our` 时,它将在 `GLOBAL` 符号表中注册。
词法别名和安全性§
值得庆幸的是,Raku 保护我们不会意外地覆盖调用站点定义(例如内置函数)。考虑以下对 `Bar.rakumod` 的添加
our sub say ()
这将创建一个词法别名,隐藏了 `Bar.rakumod` 内部的 `say` 内置函数,但保持调用者的 `say` 不变。因此,以下对 `say` 的调用仍然按预期工作
use Bar;say 'Carry on, carry on...'; # OUTPUT: «Carry on, carry on...»