重写为对应第三个版本 https://stackoverflow.com/revisions/62131486/3你的回答。
[ 1 ] 将“require”与文字一起使用
在这种情况下,编译器会检查是否MODULE
已经被声明为符号。如果没有,编译器会声明它,并将其绑定到刚刚为此“require”创建的空占位符包
To be a bit more specific, the require
keyword, and the code generated by it4, does the work.
它创建符号的唯一原因是这样人们可以编写该标识符并且代码可以编译。如果require
如果不这样做,那么使用标识符的代码将无法编译,即使require FOO
就会成功:
require FOO;
my FOO $bar; # Type 'FOO' is not declared
# MODULE 不存在,因此查找结果为编译阶段占位符包:MODULE
MODULE
does存在。并且查找成功。它返回绑定到的值MODULE
符号,这是占位符包require
在编译阶段放在那里。
# 虽然执行顺序require
在查找之后出现
的执行require
的编译阶段动作来了before在运行阶段发生的查找。
[ 2 ] 将“require”与字符串一起使用**
如果找到(具有适当的内容:模块、包等),那么它会在当前范围内创建一个命名空间,并使用文件的内容加载它。
我认为唯一的符号声明require
所做的是代码编写者已将其明确编写为静态标识符作为require
陈述。例子:
-
require MODULE <A>;
-->MODULE
and A
.
-
require 'MODULE.pm6' <A>;
--> A
.
-
require ::('MODULE') <A>;
--> A
.
Aiui the MLS1, as part of symbol merging (P6M), declares further symbols as necessary. But this work isn't being done by require
. It's done by MLS on its behalf. And it isn't peculiar to require
. It's the same (sort of) work that happens during the compile-phase as a result of a use
statement.
[ 3 ] 使用“require”进行动态查找
{ try require ::('MODULE'); }
I have code that is an attempt to demonstrate that this does not do a lookup before attempting to load the module.2
在我看来,在这种情况下,“require”的行为不是“正常”子例程。
require
不是例行公事,正常或其他。
say require MODULE; # Undeclared name:
MODULE used at line 1
# Undeclared routine:
require used at line 1
如果您搜索require
in 官方文档 https://docs.raku.org/你会看到它not列出在日常参考部分而是模块部分语言参考。它是一个关键字、一条语句、编译器可以理解的语言的特殊部分。
如果“require”的行为类似于“正常”子例程,那么它可以使用的唯一输入将是其后面的动态查找的结果(命名空间或失败)。
动态查找的结果是绑定到 a 的值Symbol https://docs.raku.org/language/glossary#index-entry-Symbol,如果已声明,或者Failure
否则:
my $variable = 42;
say ::('$variable'); # 42
say ::('nonsense') ~~ Failure; # True
$variable
不是命名空间。
但事实上,在失败的情况下(作为动态查找的结果),“require”继续在存储库中搜索适当的包(通常情况下,仍然使用我们为动态查找提供的参数: '模块')。
Given the code I wrote tracking dynamic lookup of the value of ::('MODULE')2 it looks likely to me that there is no dynamic lookup of it by any code, whether require
or the MLS, if the module loading fails.
这反过来意味着它只会发生,如果有的话,期间或之后(成功)加载模块。所以要么是美国职业足球大联盟正在这样做(看起来最有可能),要么,也许,require
正在做after该模块已成功加载(似乎不太可能,但我还没有准备好 100% 消除它)。
{ modified_dynamic_lookup('MODULE') :if_symbol_not_found_search_repositories_and_if_appropriate_package_found_create_namespace_and_load_package_contents; }
我想我已经证明根本没有查找require
或者MLS,或者,如果它做到了,那也只是after模块已成功加载。
编译器首先执行什么步骤,然后运行时执行什么步骤?
This answer is of course an attempt to answer that but my brief compiler code analysis may be of some help.3 (Though clicking the link to see the actual code in Actions.nqp
is not for the faint of heart!)
[ 后记 ]
从这个意义上说,紧随 require“指令”的“动态查找构造”有两件事用:
-
通知编译器该构造是“动态的”(因此不必在编译时修复任何内容)
-
提供将用于搜索符号、命名空间、文件或存储库内容的字符串
我认为它只做了 2 个事情,只是传递给 MLS 的包名称。
当我们加载相同的库“require”时不会抛出任何异常。它是否默默地忽略加载的库?
我不认为require
对此一无所知。它将其交给 MLS,然后在 MLS 完成其任务后接手。我不认为require
可以区分 MLS 何时成功执行新加载以及何时跳过加载。它所知道的只是美国职业足球大联盟是否表示一切都很好还是有例外。
当它可以首先检查相同的名称空间是否已在使用中时,为什么要费心做这么多工作呢?
何必费心去做any当 MLS 已经这样做时才工作,并且require
无论如何都会调用 MLS 吗?正在做anything是白费力气。
All require
所要做的就是处理编译阶段用户明确输入的符号require
陈述。它can't要求美国职业足球大联盟处理这些问题,因为这与成功的模块加载,这是 MLS 摆弄符号的唯一场景。
相反,当我们假装加载不同的库时,它会抛出异常:正在使用的符号的“重复定义”。
尝试这个:
require ::('foo');
require ::('other');
现在当你改变时再试一次unit module foo;
in foo.pm6
and other.pm6
to unit module bar;
。您仍然会得到相同的异常,但符号将是bar
。怎么能require
知道关于bar
?不可以。例外情况来自 MLS,并且只有 MLS 知道该符号。
因此我得出结论,也许“require”首先检查与提供的字符串同名的命名空间。
除非你把MLS算作require
,我相信你现在可以看出你的“也许”资格是明智的。 :)
我偶然发现了一个奇怪的行为...在 (3) 中查找 'foo::A' 没有找到任何东西!
对此我有一个解释。我不是说这是对的,但似乎并不对too当我写下这篇文章时,我感到很奇怪:
The use
语句加载foo.pm6
包裹。它定义了一个包foo
,其中包含一个类A
,并出口A
。这会导致导入词法范围中的符号foo
,它绑定到一个包,该包包含一个符号A
。它还会在导入词法范围中产生另一个符号,A
.
The require
语句加载other.pm6
包裹。它定义了一个包foo
,其中包含一个类B
,并出口B
。这会导致重新绑定foo
将词法范围中的符号导入到不同的包,即包含该符号的新包B
。它还会在导入词法范围中产生另一个符号,B
.
较早的A
徘徊。 (换句话说,P6M符号合并过程不包括removing符号。)但是foo::A
,在绑定到的包中查找foo
符号,不再存在,因为包绑定到foo
符号现在是来自other.pm6
包,覆盖了来自foo.pm6
包裹。
与此同时,还有一个奇怪的现象:
try require ::($name);
with $! {.say}; # No such symbol 'other' ...
我认为这反映了require
进行(失败的)查找after a 成功的模块负载。
Note that this message does not appear if the module fails to load; this seems to again confirm my thinking (and code2) that require
does not do any lookup until after a successful load (if that; I still don't have a strong sense about whether it's the MLS that's doing this stuff or the require
; the code4 is too complex for me atm).
对您的评论的回复
根据您对此答案的评论:
就像我们将 require +“动态查找公式”合并的结果一样,得到了这样的增强型动态查找{ ::('something') :if_not_found_as_namespace_check_repositories_and_load }
由于各种原因,这对我来说并不真实。
例如,假设有一个包foo
声明为module foo { our sub bar is export { say 99 } }
如果require
d.现在考虑这段代码:
my \foo = 42;
say ::('foo'); # 42
require ::('foo') <&bar>;
say foo; # 42
bar; # 99
这对我来说很有意义。它不会加载名称为的包42
。它不会查找符号foo
。它将加载名称为的包foo
。虽然它可能会查找符号foo
after加载包,它won't已经安装了一个符号foo
因为已经有一个了。
脚注
1 By Module Loading Subsystem I mean the various parts that, given a module name, do things like searching the local file system, or a database, checking precompilation directories, invoking compilation, and merging symbols if a module successfully loads. I don't know where the boundaries are between the parts, and the parts and the compiler. But I'm confident they are not part of require
but merely invoked by it.
2 Run this code:
my \MODULE =
{ my $v;
Proxy.new:
FETCH => method { say "get name: $v"; $v },
STORE => method ($n) { say "set name: $n"; $v = $n }}();
MODULE = 'unseen by `require`';
say ::('MODULE');
use lib '.';
say 'about to `require`';
require ::('MODULE');
3 We start with the relevant match in Raku's Grammar.nqp file https://github.com/rakudo/rakudo/blob/62adc88c69ab2532db11c573b3ccb0452811a14c/src/Perl6/Grammar.nqp#L1363-L1371:
rule statement_control:sym<require> {
<sym>
[
| <module_name>
| <file=.variable>
| <!sigil> <file=.term>
]
<EXPR>?
}
代码似乎符合我们的预期——arequire
关键字后跟:
我们感兴趣的是<module_name>
分支。它调用token module_name
哪个调用token longname
哪个调用token name
:
token name {
[
| <identifier> <morename>*
| <morename>+
]
}
Clearly ::('foo')
不以<identifier>
。所以就是token morename https://github.com/rakudo/rakudo/blob/62adc88c69ab2532db11c573b3ccb0452811a14c/src/Perl6/Grammar.nqp#L579-L591。我会删掉几行无趣的内容:
token morename {
'::'
[
|| <?before '(' | <.alpha> >
[
| <identifier>
| :dba('indirect name') '(' ~ ')' [ <.ws> <EXPR> ]
]
]?
}
答对了。这样就可以匹配了::(
,特别是:dba('indirect name') '(' ~ ')' [ <.ws> <EXPR> ]
bit.
所以此时我们将捕获:
statement_control:sym<require><module_name><longname><name><morename><EXPR>
不久之后statement_control:sym<require>
token即将成功。那么此时它就会调用中对应的action方法Actions.nqp
...
4 In Actions.nqp
we find the action corresponding to token statement_control:sym<require>
, namely method statement_control:sym<require> https://github.com/rakudo/rakudo/blob/62adc88c69ab2532db11c573b3ccb0452811a14c/src/Perl6/Actions.nqp#L2143-L2274. The opening if $<module_name> {
conditional will be True
, leading to running this code:
$longname := $*W.dissect_longname($<module_name><longname>);
$target_package := $longname.name_past;
在我看来这段代码正在剖析解析的结果::('foo')
,并将与该解剖相对应的 AST 绑定到$target_package
,而不是费心进行查找或准备运行时查找。
如果我是对的,::('foo')
不需要超过 9 个字符require
可以以任何它喜欢的方式解释它们。这里没有必要暗示它会执行任何特定的操作,例如查找,因为它构造了包加载代码。
后半段动作does进行查找。有像这样的行this https://github.com/rakudo/rakudo/blob/62adc88c69ab2532db11c573b3ccb0452811a14c/src/Perl6/Actions.nqp#L2267:
?? self.make_indirect_lookup($longname.components())
并给出例程名称,我认为is进行查找,也许作为其中的一部分require
如果包加载成功,则尝试添加包符号。