require 的行为(静态 + 动态)[ RAKU ]

2023-12-24

我的问题与以下行为有关require当与所需命名空间的静态或动态解析一起使用时。

我将尝试表达我对事物的理解:

[ 1 ] 将“require”与文字一起使用

    { require MODULE; }

在这种情况下,编译器会检查 MODULE 是否已被声明为符号。 如果没有,编译器会声明它,并将其绑定到刚刚为此“require”创建的空占位符包

{
    my $response = ::('MODULE');  # this happens at runtime
    say $response.^name;          # MODULE doesn't exist so the lookup results in the compilation-phase placeholder package: MODULE

    try require MODULE;           # although the execution order of require comes after the lookup, 
                                  # the placeholder package creation was done during compilation and the package is present in the current scope during run-time
}

[ 2 ] 将“require”与字符串一起使用

    { try require 'FILE_PATH'; }

在这种情况下,“require”试图(在运行时)查找由字符串中声明的文件名定义的文件。 如果找到(具有适当的内容:模块、包等),那么它会在当前范围内创建一个命名空间,并使用文件的内容加载它。

[ 3 ] 使用“require”进行动态查找

    { try require ::('MODULE'); }

在我看来,在这种情况下“要求”的行为NOT作为“正常”子程序。

当我们将“require”与“动态查找”一起使用时,动态查找的核心功能就会“融化”在一个新例程中,该例程的行为与我们预期的不同。

事实上,“动态查找”例程的结果要么是符号,要么是失败。

如果“require”的行为类似于“正常”子例程,那么它可以使用的唯一输入将是其后面的动态查找的结果(命名空间或失败)。

但事实上,在失败的情况下(作为动态查找的结果),“require”继续在存储库中搜索适当的包(通常情况下,仍然使用我们为动态查找提供的参数: '模块')。

显然,从这个意义上说,“require”的行为并不像“正常”子例程。

根据我的思路,require + 动态查找的组合类似于以下构造:

{ modified_dynamic_lookup('MODULE') :if_symbol_not_found_search_repositories_and_if_appropriate_package_found_create_namespace_and_load_package_contents; }

我关心的是我对案例[3]的理解。

require+动态查找是如何工作的? (从分析上讲 - 编译器首先遵循什么步骤,然后运行时遵循什么步骤?)

[ 后记 ]

我同意@raiph 的“要求”是NOT一个子例程,并且它深深地集成在语言中。

从这个意义上说,紧随 require“指令”的“动态查找构造”有两件事用:

  1. 通知编译器该构造是“动态的”(所以不要 麻烦在编译时修复任何东西)

  2. 要提供将用于搜索符号的字符串, 命名空间、文件或存储库内容

@raiph 表示他认为“require”在成功加载后会进行查找。

我对此唯一的反对意见是,当我们加载同一个库时,“require”不会抛出任何异常。

它是否默默地忽略加载的库? 当它可以首先检查相同的名称空间是否已在使用中时,为什么要费心做这么多工作呢?

相反,当我们假装加载不同的库时,它会抛出异常:正在使用的符号的“重复定义”。

为了证明我进行了以下操作:

在 ./lib 目录中,我放置了两个库,“foo.pm6”是“foo”的单元定义,其中定义了类 A:

file "foo.pm6" contents:
-----------------------------------
unit module foo;

class A is export {}

另一个库“other.pm6”,其中包含“foo”的定义,这次定义了不同的类 B。

file "other.pm6" contents:
-----------------------------------
module foo {
    class B is export {}
}

raku 程序文件包含以下内容:

use lib <lib>;

my $name = 'other';           # select one of {'other', 'foo'}

require ::('foo') <A>;        ########> Initial package loading

my $a = try ::('foo::A').new;
say '(1) ' ~ $a.^name;        # (1) foo::A

$a = ::('A').new;
say '(2) ' ~ $a.^name;        # (2) foo::A

try require ::($name);        # if $name eq 'other' => throws exception, if $name eq 'foo' => does nothing
with $! {.say};               # P6M Merging GLOBAL symbols failed: duplicate definition of symbol foo ...

$a = try ::('foo::A').new;
say '(3) ' ~ $a.^name;        # (3) foo::A

$a = ::('A').new;
say '(4) ' ~ $a.^name;        # (4) foo::A

从上面的示例中我们看到,当我们尝试重新加载 foo 命名空间时,隐藏在具有不同名称的文件中(只是为了欺骗 raku),它会抛出异常。

因此我得出结论,也许“require”首先检查与提供的字符串同名的命名空间。

顺便说一下,检查这一点时,我偶然发现了一个奇怪的行为。如下:

如果我们使用“use foo;”在行:“初始包加载”而不是“require ::('foo') ;”中,我们得到以下结果:

(1) foo::A
(2) foo::A
No such symbol 'other' ...

(3) Any
(4) foo::A

在 (3) 中查找 'foo::A' 没有找到任何东西!

此外,如果我更改库文件:“other.pm6”,其中包含以下内容(类 A 而不是 B - 如 foo.pm6 中所示)

file "other.pm6" contents:
-----------------------------------
module foo {
    class A is export {}
}

结果似乎恢复到预期:

(1) foo::A
(2) foo::A
No such symbol 'other' ...

(3) foo::A
(4) foo::A

这是一个错误还是我缺少的其他东西?


重写为对应第三个版本 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“指令”的“动态查找构造”有两件事用:

  1. 通知编译器该构造是“动态的”(因此不必在编译时修复任何内容)

  2. 提供将用于搜索符号、命名空间、文件或存储库内容的字符串

我认为它只做了 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 } }如果required.现在考虑这段代码:

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>); or

  • a <variable> (eg $foo); or

  • a <term>不是以 a 开头的<sigil>.

我们感兴趣的是<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如果包加载成功,则尝试添加包符号。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

require 的行为(静态 + 动态)[ RAKU ] 的相关文章

随机推荐

  • SOAP-ENV:WSDL 中的错误

    我创建了 SOAP Web 服务 而且我对 SOAP 还很陌生 在创建网络服务时 我面临以下问题
  • 如何将向量拆分为列 - 使用 PySpark [重复]

    这个问题在这里已经有答案了 Context 我有一个DataFrame有 2 列 单词和向量 其中 向量 的列类型是VectorUDT 一个例子 word vector assert 435 323 324 212 我想得到这个 word
  • 多输入多变量数据可视化

    我试图通过从多个输入文件中读取多元数据模型来可视化它们 我正在寻找一个简单的解决方案来可视化从多个输入 csv 文件读取的多个类别数据 没有 各个文件中输入的行数范围为 1 到 10000 格式与 4 列 csv 文件的所有输入相同 Inp
  • 从 C# 项目的资源区域加载图像

    我的项目中有一张图像存储在 Resources myimage jpg 中 如何动态将此图像加载到 Bitmap 对象中 您使用的是 Windows 窗体吗 如果您使用 属性 资源 UI 添加了图像 则可以从生成的代码访问该图像 因此您可以
  • 无需用户名的 HTTP 身份验证

    我正在 Node js 中创建一个 HTTP 服务器 并且我想向只有我会使用的页面添加密码 这Basic https developer mozilla org en US docs Web HTTP Authentication Basi
  • 如何添加节点终端Visual Studio Code?

    我尚未成功 但我重新安装了 Visual Studio Code 并且无法再次向其中添加节点终端 请不要说我也可以使用 bash 运行并使用 powershell 这两个在Vscode中默认可达 但我想更改默认终端并更改为节点终端 不是 p
  • 如何使用 ggplot2 + directlabels 的标签自定义名称

    我将 ggplot2 与 directlabels 包一起使用geom line 情节 我希望其中一个标签上写着 X M 然而 在我的data frame X M 作为列名被重命名为 X M 我找不到有关如何提供direct label具有
  • 计算身体宽度百分比时出现问题

    我需要构建一个经典的 960px 布局 并且必须将 960px 转换为 所以 我应该使用960px 16px 60 right 注 960px 主体宽度16px 字体大小 问题 body width 60 960 16px 比 body w
  • 如何禁用添加到 ASP.NET Core 日志中的 ANSI/VT100 颜色代码

    我们在 Google Cloud 中托管 ASP NET Core 服务 来自 Stack Driver 的日志始终使用 ANSI VT100 颜色代码 如下所示 40m 32minfo 39m 22m 49m 有没有办法告诉 ASP NE
  • HQL 查询检查集合大小是否为 0 或空

    我尝试生成一个 HQL 查询 其中包含具有空约会集合的用户 由 OneToMany 映射 SELECT u FROM User u JOIN u appointments uas WHERE u status 1 AND uas time
  • HSQLDB 可以处理几百万行吗?

    我正在开发一个需要数据库的单用户应用程序 大多数表都具有合理数量的数据 但也有一些表可能会增长到数百万行 我的任何查询都不会返回大型结果集 有人知道 HSQLDB 是否可以处理这么大量的行吗 From HSQLDB 官方页面 http ww
  • 内存层次结构 - 为什么寄存器昂贵?

    我明白那个 Faster访问时间 gt More昂贵的 Slower访问时间 gt Less昂贵的 我还了解到寄存器是层次结构的顶部 并且具有最快的访问时间 我很难研究的是why这么贵吗 据我所知 寄存器实际上是直接内置于 ALU 中的电路
  • 给定年份和月份的月份函数的最后一天

    多次单步执行代码后 我无法找到具体导致此错误的原因 我希望有人以前见过这个错误 这就是我的think与问题相关 Load packages library lubridate MONTH lt 1 YEAR lt 2018 Last day
  • 如何使用 ie11 在 Threejs 中加载 gltf 场景

    我使用 Three js 及其 gltfloader js 编写了一个简单的 html 来加载 gltf 模型 它在 Mozilla 上完美运行 但即使没有错误 它也不会显示在 ie11 上 我尝试过使用 es6 promise polly
  • 使用 python lxml xpath 迭代表中的所有行

    这是我想要从中提取数据的html页面的源代码 网页 该表格位于页面底部 table class clCommonGrid cellspacing 0 thead tr td Kommande matcher td tr tr th th t
  • 游戏开发方面 SharpDX 与 SlimDX 比较? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 使用 cypress 从数据库中检索值

    谁能帮我解决以下场景 我必须执行一个select语句并使用 Cypress 将查询结果存储在变量中 下面是我尝试过的代码 我想存储查询结果 select id from invoices where INumber invoiceNumbe
  • 如何在 OpenGL 中可视化深度纹理?

    我正在研究阴影贴图算法 我想调试它在第一次传递时生成的深度图 但是 深度纹理似乎无法正确渲染到视口 有没有简单的方法可以将深度纹理显示为灰度图像 最好不使用着色器 您可能需要更改深度纹理参数以将其显示为灰度级别 glTexParameter
  • 使用 scipy.optimize 动态选择要最小化 python 中函数的参数

    我有一个函数 它接受变量列表作为参数 我想使用 scipy optimize minimize 最小化这个函数 问题在于 它是在运行时决定应该对参数列表中的哪个变量进行最小化的 所有其他变量将获得固定值 让我们举个例子来说明一下 a 1 c
  • require 的行为(静态 + 动态)[ RAKU ]

    我的问题与以下行为有关require当与所需命名空间的静态或动态解析一起使用时 我将尝试表达我对事物的理解 1 将 require 与文字一起使用 require MODULE 在这种情况下 编译器会检查 MODULE 是否已被声明为符号