将类的运算符定义与其他文件分开并使用它们

Han*_*ann 9 raku

我有4个文件都在同一个目录下:main.rakumodinfix_ops.rakumodprefix_ops.rakumodscript.raku

  • main模块有一个类定义 ( class A)
  • *_ops模块有一些运算符例程定义要编写,例如,$a1 + $a2以重载的方式。
  • script.raku尝试A实例化对象并使用那些用户定义的运算符。

为什么是 3 个文件而不是 1 个?由于类定义可能很长,并且在文件中分离重载的运算符定义似乎是编写更整洁代码(更易于管理)的好主意。

例如,

# main.rakumod
class A {
    has $.x is rw;
}
Run Code Online (Sandbox Code Playgroud)
# prefix_ops.rakumod
use lib ".";
use main;

multi prefix:<++>(A:D $obj) {
    ++$obj.x;
    $obj;
}
Run Code Online (Sandbox Code Playgroud)

和类似的例程infix_ops.rakumod。现在,在 中script.raku,我的目标是仅导入主模块并查看也可用的重载运算符:

# script.raku
use lib ".";
use main;

my $a = A.new(x => -1);
++$a;
Run Code Online (Sandbox Code Playgroud)

但它自然不会看到++多个A对象,因为main.rakumod它不知道*_ops.rakumod文件的现状。有没有办法实现这一目标?如果我use prefix_opsmain.rakumod,它说'use lib' may not be pre-compiled可能是因为循环依赖

rai*_*iph 3

它说'use lib' may not be pre-compiled

  • “可能”这个词是有歧义的。实际上它不能被预编译。

  • 如果消息中包含“不要放入use lib模块”这样的内容,效果会更好。

现在,这个问题已根据下面的 @codesections++ 评论得到修复。

也许是因为循环依赖

No.use lib只能由主程序文件使用,即由 Rakudo 直接运行的文件。

有什么方法可以实现这个目标吗?

这是一种方法。

我们引入了一个use由其他包创建的新文件,以消除循环。现在我们有四个A文件(我已经合理化了有助于类型的包的命名或变体A):

  1. A-sawn.rakumod这是一个roleclass类似的:

    unit role A-sawn;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 其他包将被分离到自己的文件中,use新的“sawn”包和does/或is它(视情况而定):

    use A-sawn;
    
    unit class A-Ops does A-sawn;
    
    multi  prefix:<++>(A-sawn:D $obj) is export { ++($obj.x) }
    multi postfix:<++>(A-sawn:D $obj) is export { ($obj.x)++ }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 该类型A.rakumod的文件A执行相同的操作。它还use可以将任何其他包拉入同一A名称空间;这将根据 Raku 的标准导入规则从中导入符号。然后显式导出相关符号:

    use A-sawn;
    use A-Ops;
    sub EXPORT { Map.new: OUTER:: .grep: /'fix:<'/ }
    
    unit class A does A-sawn;
    has $.x is rw;
    
    Run Code Online (Sandbox Code Playgroud)
  4. 最后,完成此设置后,主程序可以use A;

    use lib '.';
    use A;
    
    my $a = A.new(x => -1);
    say $a++; # A.new(x => -1)
    say ++$a; # A.new(x => 1)
    say ++$a; # A.new(x => 2)
    
    Run Code Online (Sandbox Code Playgroud)

这里的两个主要内容是:

  • 引入一个(空)A-sawn

    这种类型使用@codesection对解决循环模块加载的最佳方法的答案中所示的技术来消除循环。

    Raku 文化有一个有趣的通用术语/模因,用于描述解决圆形问题的技术:“圆锯”。因此,在使用此技术时,我使用了-sawn“sawn”类型名的后缀作为约定。[1]

  • 将符号导入包中然后重新导出

    这是通过sub EXPORT { Map.new: ... }. [2]请参阅文档sub EXPORT

    必须Map包含符号列表PairOUTER::对于这种情况,我已经从伪包中查找了键,该包引用了紧邻出现在sub EXPORT其中的词法范围的符号表OUTER::。这当然是词法范围,一些符号(对于运算符)刚刚被导入到其中use Ops;陈述。fix:<然后我用 grep 查找包含;的键的符号表。这将捕获名称中包含该字符串的所有符号键(soinfix:<...prefix:<...)。根据需要更改此代码以满足您的需求。[3]

脚注

[1]就目前情况而言,这种技术意味着提出一个与use新类型的消费者所指定的名称不同的新名称,并且不会与任何其他包冲突。这表明有一个后缀。我认为-sawn对于一个不寻常的、独特的和助记符的后缀来说这是一个合理的选择。也就是说,我想有人最终会将这个过程打包成一种新的语言结构,该结构在幕后完成工作,生成名称并自动消除人们必须使用所示技术对包进行的手动更改。

[2]至关重要的一点是,如果 asub EXPORT要做你想做的事,它必须放在它所应用的包定义之外。这又意味着它必须位于包声明之前unit。这反过来又意味着use它所依赖的任何语句都sub EXPORT必须出现在相同或外部的词法范围内。(文档中对此进行了解释,但我认为值得在这里进行总结,以尝试避免太多的麻烦,因为如果它位于错误的位置,则不会出现错误消息。)

[3]与脚注 1 中讨论的循环锯方面一样,我想有人最终也会将这种导入和导出机制打包成一个新的构造,或者更好的是 Raku 内置语句的增强use