将 class 的运算符定义分离到其他文件并使用它们

Separating operator definitions for a class to other files and using them

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

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

例如,

# main.rakumod
class A {
    has $.x is rw;
}
# prefix_ops.rakumod
use lib ".";
use main;

multi prefix:<++>(A:D $obj) {
    ++$obj.x;
    $obj;
}

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

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

my $a = A.new(x => -1);
++$a;

但它自然不会看到 ++ multi for A objects 因为 main.rakumod 不知道 *_ops.rakumod 文件。有什么办法可以做到这一点?如果我在 main.rakumoduse prefix_ops,它说 'use lib' may not be pre-compiled 可能是因为循环依赖性

it says 'use lib' may not be pre-compiled

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

  • 如果消息的意思是“不要将 use lib 放入模块中”,则该消息会更好。

现在已根据@codesections++ 下面的评论修复了此问题。

perhaps because of circular dependentness

没有。 use lib只能被主程序文件使用,Rakudo直接运行

Is there a way I can achieve this?

这是一种方法。

我们引入了一个由其他包 used 生成的新文件,以消除循环性。所以现在我们有 四个 文件(我已经合理化命名以坚持 A 或它的变体,用于有助于类型 A 的包):

  1. A-sawn.rakumod 那是 roleclass 或类似的:

    unit role A-sawn;
    
  2. 其他要分离到自己文件中的包 use 新的“sawn”包和 doesis 视情况而定:

    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)++ }
    
  3. A 类型的 A.rakumod 文件做同样的事情。它还 uses 任何其他包将被拉入同一个 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;
    
  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)
    

这里主要有两点:

  • 引入一个(空)A-sawn

    此类型使用 中所示的技术消除了循环。

    乐文化有一个有趣的通用技术 term/meme,用于解决循环问题的技术:"circular saws"。因此,在使用此技术时,我使用了“sawn”类型名称的 -sawn 后缀作为约定。[1]

  • 正在将 符号 导入包中,然后然后 重新导出他们

    这是通过 sub EXPORT { Map.new: ... }.[2] 完成的,参见 the doc for sub EXPORT.

    Map 必须包含符号列表 (Pairs)。对于这种情况,我已经通过 OUTER:: pseudopackage 中的键进行了搜索,这些键指的是词法范围的符号 table 紧接在 sub EXPORT 之外, OUTER:: 出现在其中。这是当然,use Ops; 语句刚刚 导入了 某些符号(用于运算符)的词法范围。然后我用 grep 符号 table 来查找包含 fix:< 的键;这将捕获名称中带有该字符串的所有符号键(因此 infix:<...prefix:<... 等)。根据需要更改此代码以满足您的需要。[3]

脚注

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

[2] 至关重要的一点是,如果一个sub EXPORT是为所欲为,它必须放置在外部它适用的包定义。而这又意味着它必须是 before 一个 unit 包声明。这反过来意味着 sub EXPORT 所依赖的任何 use 语句必须出现在相同或外部词法范围内。 (这在文档中有解释,但我认为在这里进行总结是值得的,因为如果它在错误的地方,就不会出现错误消息。)

[3] 至于脚注 1 中讨论的圆锯方面,我想有人最终也会打包这个进口- and-export 机制到一个新的构造中,或者,也许更好,Raku 内置 use 语句的增强。

你好@hanselmann 我会这样写(在 3 个文件/同一个目录中):

定义我的 class(es):

# MyClass.rakumod
unit module MyClass;

class A is export {
    has $.x is rw; 
}

定义我的运算符:

# Prefix_Ops.rakumod
unit module Prefix_Ops;

use MyClass;

multi prefix:<++>(A:D $obj) is export {
    ++$obj.x;
    $obj;
}

运行 我的代码:

# script.raku
use lib ".";
use MyClass;
use Prefix_Ops;

my $a = A.new(x => -1);
++$a;

say $a.x;   #0

根据 Module docs 的提示,我在做一些不同的事情:

  • 避免使用 main(或 Main,或 MAIN)---我担心 MAIN 是一个保留名称,只是想避免使用任何(很酷的)机制
  • 在每个 'rakumod' 文件的顶部引入单元模块声明……也许可以在 Raku 中使用裸文件……但我从未尝试过,我会说它是从文档中并不明显,它甚至是可能的,或者支持
  • 既然我想让它在第一次工作时您会注意到我使用了相同的文件名和模块名……同样,也可以采用不同的方式(一个文件中的多个模块等)。 ..但我也没试过
  • 在我希望我的脚本能够使用这些定义的地方使用 'is export' 特征...正如您通过仔细研究文档所知道的那样 ;-) 是每个模块都有自己的命名空间( “stash”),我们需要导出以将导出的定义推入脚本的命名空间
  • 正如@raiph 提到的,您只需要脚本来定义模块库位置
  • 既然你想让你的前缀 multi “知道” class A 那么你还需要在 Prefix_Ops 模块中使用 MyClass

无论如何,总而言之,我认为 raku 模块系统体现了“简单的事情容易和困难的事情认为可行”的独特组合......我与你的代码有关(非常接近) 调整了一些文件名并加入了一些简洁的概念,如 'unit module' 和 'is export',它看起来并没有太大的不同,因为 raku 将所有 import/export 机器保持在表面之下,就像天鹅滑翔一样过河...