模块包
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
确认了底层的元类 M
是 Raku::Metamodel::ModuleHOW
。
何时使用模块
模块主要用于封装不属于类或角色定义的代码和数据。模块内容(类,子程序,变量等)可以从具有 is export
trait 的模块中导出; 一旦import
或 use
了模块,这些内容在调用者的命名空间中就可用了。模块还可以选择性地在其命名空间中通过 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
) 有时被称为“模块”,但它们实际上只是在您写了 need
,use
或者 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
; 在这种情况下,我们可能想要重命名 M
为 Foo
。修改后的文件将为:
module Foo {
sub greeting ($name = 'Camelia') { "Greetings, $name!" }
our sub loud-greeting (--> Str) { greeting().uc }
sub friendly-greeting is export { greeting('friend') }
}
可被调用者更一致地使用(注意 use Foo
和 Foo::
之间的关系):
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
也可以用于 class
,grammar
和 role
)。
如果我省略了module
会发生什么?
为了更好地理解在 Foo.pm6
中 module
声明符在做什么,让我们将它与变体文件 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...»