当前位置: 首页 > 知识库问答 >
问题:

需求行为(静态-动态)[RAKU]

陆飞鸿
2023-03-14

我的问题与require在用于所需名称空间的静态或动态解析时的行为有关。

我会试着表达我对事物的理解:

[1]使用需要字面

    { require 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]使用需要与字符串

    { try require 'FILE_PATH'; }

在这种情况下,“要求”是试图找到(在运行时)由字符串中声明的文件名定义的文件。如果找到(包含适当的内容:模块、包等),它将在当前作用域中创建一个命名空间,并将其加载为文件的内容。

[3]在动态查找中使用“require”

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

在我看来,在这种情况下,“require”的行为不像“普通”子例程。

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

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

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

但是在失败的情况下(作为动态查找的结果),“需要”继续在存储库中搜索合适的包(通常情况下,仍然使用我们给动态查找的参数:“模块”),这也是一个事实。

所以很明显,“要求”在这个意义上并不像一个“正常”的子程序。

根据我的思路,需求动态查找的组合类似于以下结构:

{ modified_dynamic_lookup('MODULE') :if_symbol_not_found_search_repositories_and_if_appropriate_package_found_create_namespace_and_load_package_contents; }

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

需要动态查找是如何工作的?(从分析角度讲,编译器首先要遵循哪些步骤,然后才是运行时?)

[后脚本]

我同意@raiph的观点,即“require”不是一个子程序,它与语言有着深刻的整合。

从这个意义上讲,require“指令”后面的“动态查找结构”用于两件事:

>

  • 通知编译器该构造是“动态的”(因此不必在编译时修复任何东西)

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

    @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”首先检查与所提供字符串具有相同名称的名称空间。

    顺便说一句,在检查这件事时,我偶然发现了一种奇怪的行为。以下是:

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

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

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

    此外,如果我将库文件“other.pm6”更改为以下内容(类A而不是foo.pm6中的类B)

    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
    

    是一只虫子还是我遗漏了什么?

  • 共有2个答案

    佘飞鸣
    2023-03-14

    require可以在编译过程中执行一些操作。

    require Module;
    say Module;
    

    它假定加载该模块将为您提供名为module的内容。

    因此,它会在编译时安装一个具有该名称的临时符号。

    这是它在编译时唯一做的事情<(所以当我说“一些”时,我撒了个小谎

    if Bool.pick {
        require module-which-does-not-exist;
    
        module-which-does-not-exist.method-call()
    }
    

    大约有一半的时间,上面所说的什么都不做
    它在运行时抱怨找不到模块的另一半时间。

    (我选择了Bool.pick而不是False,因此编译时优化器肯定无法对其进行优化。)

    当您用标识符以外的东西调用它时,它在编译时不知道模块将是什么。因此它无法创建临时名称空间。

    require 'Module';
    say Module; # COMPILE ERROR: undeclared name
    
    require Module; # RUNTIME ERROR: can't find 'Module'
    say Module;
    
    require 'Module'; # RUNTIME ERROR: can't find 'Module'
    say ::('Module');
    
    if False {
        require Module;
        say Module;
    }
    # no error at all
    
    if False {
        require 'Module';
        say ::('Module');
    }
    # no error at all
    
    笪建章
    2023-03-14

    重写以对应于您答案的第三个版本。

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

    更具体地说,require关键字以及由它生成的代码4起作用。

    它创建符号的唯一原因是,人们可以编写标识符,代码将被编译。如果require没有这样做,那么使用标识符的代码将无法编译,即使require FOO会成功:

    require FOO;
    my FOO $bar; # Type 'FOO' is not declared
    

    #模块不存在,因此在编译阶段占位符包中查找结果:模块

    MODULE确实存在。查找成功。它返回绑定到MODULE符号的值,该符号是在编译阶段需要放在那里的占位符包。

    #虽然需要的执行顺序在查找之后

    执行需要的编译阶段操作在运行阶段发生的查找之前。

    如果找到(包含适当的内容:模块、包等),那么它将在当前范围内创建一个名称空间,并使用文件的内容加载它。

    我认为require符号的唯一声明是代码编写器作为require语句的一部分显式编写的静态标识符。示例:

    >

  • require模块

    需要'MODULE. pm6'

    require::(“模块”)

    Aiui MLS1,作为符号合并(P6M)的一部分,根据需要声明更多符号。但是这项工作不是由require完成的。这是由MLS代表它做的。而且它不是require所特有的。这与编译阶段由于use语句而发生的工作是相同的。

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

    我有一段代码试图证明,在尝试加载模块之前,这不会进行查找2

    在我看来,在这种情况下,“require”的行为不像“普通”子例程。

    require不是例行程序,正常或其他。

    say require MODULE;   # Undeclared name:
                                MODULE used at line 1
                          # Undeclared routine:
                                require used at line 1
    

    如果您在官方文档中搜索需要,您会看到它没有列在常规参考部分,而是列在语言参考的模块部分。它是一个关键字,一个语句,编译器理解的语言的一个特殊部分。

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

    动态查找的结果是绑定到符号(如果已声明)的值,或者失败否则:

    my $variable = 42;
    say ::('$variable');           # 42
    say ::('nonsense') ~~ Failure; # True
    

    $variable不是命名空间。

    但是在失败的情况下(作为动态查找的结果),“需要”继续在存储库中搜索合适的包(通常情况下,仍然使用我们给动态查找的参数:“模块”),这也是一个事实。

    考虑到我编写的跟踪动态查找:('MODULE')2值的代码,在我看来,如果模块加载失败,无论是require还是MLS,都可能没有任何代码对其进行动态查找。

    这反过来意味着它只会在模块加载期间或之后(成功加载)发生。因此,要么MLS正在这样做(似乎最有可能),要么,require在模块成功加载后正在这样做(似乎不太可能,但我还没有准备好100%消除它)。

    {modified_dynamic_lookup('MODULE'):if_symbol_not_found_search_repositories_和if_if_property_package_found_创建_namespace_和_load_package_contents;}

    我想我已经证明了要么根本没有要求或MLS的查找,要么,如果它这样做了,它只是在成功加载模块之后。

    编译器首先遵循哪些步骤,然后是运行时遵循哪些步骤?

    这个答案当然是试图回答这个问题,但我的简短编译器代码分析可能会有所帮助3(点击链接查看操作中的实际代码。nqp不适用于心脏虚弱者!)

    从这个意义上讲,require“指令”后面的“动态查找结构”用于两件事:

    >

  • 通知编译器该构造是“动态的”(因此不必在编译时修复任何东西)

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

    我认为它只做2,只是一个传递给MLS的包名称。

    当我们加载同一个库时,“require”不会引发任何异常。它是否会自动忽略加载的库?

    我认为require对此一无所知。它把它交给大联盟,然后在大联盟完成任务后再接。我认为require无法区分MLS何时成功地进行了新加载,何时跳过了加载。它只知道MLS是说一切都好还是有例外。

    当它可以首先检查相同的命名空间已经在使用时,为什么要费心做这么多工作呢?

    既然MLS已经做了任何工作,而且require无论如何都要调用MLS,为什么还要费心去做这些工作呢?做任何事都是白费力气。

    require所要做的就是处理用户在require语句中显式键入的编译阶段符号。它不能要求MLS处理这些问题,因为这与成功加载模块无关,而这是MLS处理符号的唯一情况。

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

    试试这个:

    require ::('foo');
    require ::('other');
    

    现在,当您更改单元模块foo foo中。pm6和其他。pm6单元模块条 。您仍然会得到相同的异常,但符号将是如何要求了解条码?不能。例外情况来自MLS,符号只有MLS知道。

    因此,我得出结论,可能“require”首先检查与所提供字符串具有相同名称的名称空间。

    除非您将MLS计算为require的一部分,否则我相信您现在可以看到您的“可能”资格是明智的:

    我偶然发现一种奇怪的行为。。。在(3)中查找'foo::A'没有找到任何内容!!!

    我有一个解释。我并不是说这是对的,但在我写这篇文章时,我觉得这并不奇怪:

    use语句加载foo。pm6包装。它定义了一个包foo,其中包含一个类a,并导出a。这将导致导入词法范围foo中出现一个符号,该符号绑定到一个包,该包包含一个符号a。它还导致导入词法范围中的另一个符号,A

    require语句加载other。pm6包装。它定义一个包foo,其中包含一个类B,并导出B。这将导致将导入词法范围中的foo符号重新绑定到另一个包,即包含符号B的新包。它还导致导入词法范围中的另一个符号,B

    前面的A挂起。(换句话说,P6M符号合并过程不包括删除符号。)但是在绑定到foo符号的包中查找的foo::A不再存在,因为绑定到foo符号的包现在是另一个中的包。pm6包,覆盖了来自foo的包。pm6包装。

    与此同时,还有另一个奇怪之处:

    try require ::($name);
    with $! {.say};             # No such symbol 'other' ...
    

    我认为这反映了在成功的模块加载后需要进行(失败的)查找。

    请注意,如果模块未能加载,则不会显示此消息;这似乎再次证实了我的想法(代码2),即require在成功加载之前不会进行任何查找(如果是这样的话;我仍然不清楚是MLS在做这些事情还是require;代码4对我来说太复杂了)。

    从你对这个答案的评论来看:

    它就像我们得到的结果,需要'动态查找公式'的合并,一个增强的动态查找像这样{::('something'):如果没有找到\u作为\u名称空间\u检查\u存储库\u和\u加载}

    由于种种原因,这对我来说似乎不是真的。

    例如,假定有一个包<代码> FoO <代码>声明为“代码>模块FoO {我们的子条是导出{ 99 } } /代码>如果<代码>要求< /代码>,将成功加载。现在考虑这个代码:

    my \foo = 42;
    say ::('foo');             # 42
    require ::('foo') <&bar>;
    say foo;                   # 42
    bar;                       # 99
    

    这对我来说很有意义。它不会加载名为42的包。它不会查找符号foo。它将加载名为foo的包。虽然它可能会在加载包后查找symbolfoo,但它不会安装symbolfoo,因为已经有一个了。

    1通过模块加载子系统,我指的是在给定模块名称的情况下,执行搜索本地文件系统或数据库、检查预编译目录、调用编译以及在模块成功加载时合并符号等操作的各个部分。我不知道部件之间的界限在哪里,部件和编译器之间的界限在哪里。但我相信它们不是require的一部分,而只是由它调用。

    2运行此代码:

    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我们从Raku的语法中的相关匹配开始。nqp文件:

      rule statement_control:sym<require> {
            <sym>
            [
            | <module_name>
            | <file=.variable>
            | <!sigil> <file=.term>
            ]
            <EXPR>?
        }
    

    代码似乎遵循了我们所期望的-一个要求关键字,然后是:

    >

    a

    a

    我们对

    token name {
            [
            | <identifier> <morename>*
            | <morename>+
            ]
    }
    

    显然,:('foo')不是以

        token morename {
            '::'
            [
            ||  <?before '(' | <.alpha> >
                [
                | <identifier>
                | :dba('indirect name') '(' ~ ')' [ <.ws> <EXPR> ]
                ]
            ]?
        }
    

    答对 了这将匹配::(),尤其是:dba('indirect name')('~')[

    因此,在这一点上,我们将捕获:

    statement_control:sym<require><module_name><longname><name><morename><EXPR>
    

    不久之后,语句\u控件:sym

    4Actions.nqp中,我们找到对应于令牌statement_control:sym的操作

    $longname := $*W.dissect_longname($<module_name><longname>);
    $target_package := $longname.name_past;
    

    在我看来,这段代码正在剖析解析::('foo')的结果,并将对应于该剖析的AST绑定到$target_package,而不是费心进行查找或准备运行时查找。

    如果我是对的,那么::('foo')不需要超过9个字符,而需要来解释它们,尽管它喜欢解释它们。这里没有必要暗示它在构造包加载代码时会执行任何特定的操作,例如查找。

    动作的后半部分进行查找。有这样几行:

    ?? self.make_indirect_lookup($longname.components())
    

    给定例程名称,我假设它正在进行查找,可能是whererequire尝试添加包符号的一部分,如果包加载成功。

  •  类似资料:
    • 问题内容: 在我的数据库的多个地方,开发人员使用了动态sql而不是静态sql。他们说这是为了提高性能。有人可以告诉我动态sql是否真的可以提高存储过程或plsql块的性能吗? 哪个执行速度更快,为什么? 1。 2。 问题答案: 您的示例代码非常简单,几乎没有什么区别,但是在那种情况下,静态版本最有可能执行得更好。 使用动态SQL来提高性能的主要原因是,当SQL语句发生重大变化时- 例如,您可以根据

    • 要求:在移动设备上查看基于XFA的PDF。 我尝试过的选项: 由于Adobe移动阅读器不支持XFA,因此我可以将XFA展平为静态PDF。我尝试过,但无法使用iText将动态XFA转换为静态PDF。 后来我尝试使用“Adobe PDF”作为打印服务打印XFA表单。这在手动执行时可以正常工作,但在通过代码执行时会以某种方式清除表单数据。 下面是打印任务的示例代码。已为“Adobe PDF”打印服务安装

    • 我是新手,请放心。目前,我正在使用放心的API自动化。我有以下方案要处理 我们有两个 API(例如:API1、API2),API1 会给出用户详细信息的列表。我需要将这些详细信息作为第二个 API 请求的一部分发送。 API1 -响应 API2 -请求:。:在API2请求中,我需要发送“sourceUserId”和“source”详细信息。 userSourceMeta详细信息会根据用户动态变化。

    • 问题内容: 在Linux上的“ C”上, 我需要静态库来静态链接,还是需要足够的共享库?如果没有,为什么不呢?(它们不包含相同的数据吗?) 问题答案: 是的,您需要静态库来构建静态链接的可执行文件。 静态库是编译对象的捆绑包。静态链接到库时,实际上与获取该库的编译结果,将它们解压缩到当前项目中以及将它们当作自己的对象使用一样。 动态库已链接。这意味着一些信息,例如重定位,已经被修复并丢弃。 此外,

    • 上面是一段代码,它以某种形式出现在我即将进行的一次考试的过去试卷中。这个问题的想法是衡量你是否完全理解多态性,动态和静态铸造。面向对象的基本思想。 我想把我认为正确的东西拿出来,如果人们能纠正我或补充一点,我将不胜感激。 我看不出任何与上面代码相关的其他观点。

    • 一个机器或语言是用来计算的,根据计算机模型的定义,其操作的是存储器里的数据,就现在流行的用二进制实现的计算机中(曾出现过三进制计算机),数据本质都是0和1,但机器毕竟是为人服务的,解决的是现实问题,它操作的数据需要有现实的含义,所以基本上所有语言都有类型系统,即便是汇编和机器语言,也有整数和浮点的区别 类型系统是很复杂的理论,值得用一本厚书来写,好点的资料有《类型与程序设计语言》,但我没看懂,因为