我有4个文件都在同一个目录下:main.rakumod,infix_ops.rakumod,prefix_ops.rakumod和script.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_ops在main.rakumod,它说'use lib' may not be pre-compiled可能是因为循环依赖
它说
'use lib' may not be pre-compiled
“可能”这个词是有歧义的。实际上它不能被预编译。
如果消息中包含“不要放入use lib模块”这样的内容,效果会更好。
现在,这个问题已根据下面的 @codesections++ 评论得到修复。
也许是因为循环依赖
No.use lib只能由主程序文件使用,即由 Rakudo 直接运行的文件。
有什么方法可以实现这个目标吗?
这是一种方法。
我们引入了一个use由其他包创建的新文件,以消除循环。现在我们有四个A文件(我已经合理化了有助于类型的包的命名或变体A):
A-sawn.rakumod这是一个role或class类似的:
unit role A-sawn;
Run Code Online (Sandbox Code Playgroud)
其他包将被分离到自己的文件中,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)
该类型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)
最后,完成此设置后,主程序可以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包含符号列表Pair。OUTER::对于这种情况,我已经从伪包中查找了键,该包引用了紧邻出现在sub EXPORT其中的词法范围的符号表OUTER::。这当然是词法范围,一些符号(对于运算符)刚刚被导入到其中use Ops;陈述。fix:<然后我用 grep 查找包含;的键的符号表。这将捕获名称中包含该字符串的所有符号键(soinfix:<...等prefix:<...)。根据需要更改此代码以满足您的需求。[3]
[1]就目前情况而言,这种技术意味着提出一个与use新类型的消费者所指定的名称不同的新名称,并且不会与任何其他包冲突。这表明有一个后缀。我认为-sawn对于一个不寻常的、独特的和助记符的后缀来说这是一个合理的选择。也就是说,我想有人最终会将这个过程打包成一种新的语言结构,该结构在幕后完成工作,生成名称并自动消除人们必须使用所示技术对包进行的手动更改。
[2]至关重要的一点是,如果 asub EXPORT要做你想做的事,它必须放在它所应用的包定义之外。这又意味着它必须位于包声明之前unit。这反过来又意味着use它所依赖的任何语句都sub EXPORT必须出现在相同或外部的词法范围内。(文档中对此进行了解释,但我认为值得在这里进行总结,以尝试避免太多的麻烦,因为如果它位于错误的位置,则不会出现错误消息。)
[3]与脚注 1 中讨论的循环锯方面一样,我想有人最终也会将这种导入和导出机制打包成一个新的构造,或者更好的是 Raku 内置语句的增强use。