如何使用包含where的抽象多方法?

Tyi*_*yil 8 oop abstract-class where abstract perl6

我正在研究Perl 6模块,Pod::To::Anything以便创建一个简单的界面来创建Perl 6 Pod格式化程序.我正在将渲染分成多个render方法,每个方法都必须处理Pod规范的给定部分.

为了确保完成基于此类的Pod格式化程序,我想添加涵盖所有可能的Pod对象的抽象方法.但是,这要求我where多次使用一个子句:

multi method render (Pod::Block::Named:D $ where *.name eq "NAME" --> Str) { … }
Run Code Online (Sandbox Code Playgroud)

我试图按如下方式实现它:

multi method render (Pod::Block::Named:D $pod where *.name eq "NAME" --> Str) { ".TH {self.pod-contents($pod)}\n" }
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试运行该程序时,Perl 6抱怨一种方法没有实现:

===SORRY!=== Error while compiling /home/tyil/projects/personal/perl6-Pod-To-Man/lib/Pod/To/Man.pm6 (Pod::To::Man)
Multi method 'render' with signature :(Pod::To::Man $: Pod::Block::Named:D $ where { ... }, *%_ --> Str) must be implemented by Pod::To::Man because it is required by a role at /home/tyil/projects/personal/perl6-Pod-To-Man/lib/Pod/To/Man.pm6 (Pod::To::Man):7
Run Code Online (Sandbox Code Playgroud)

这是LTA,Perl 6在where这里隐藏了部件的实际内容,但这不是我的主要问题.我遇到的问题是它告诉我我实现的方法没有实现.

我把它打了一小段以确保这不是我当前代码库的特定问题:

role Foo { multi method test(%foo where *<bar>) { … } }
class Bar does Foo { multi method test(%foo where *<bar>) { "Implementation" } }
Run Code Online (Sandbox Code Playgroud)

此代码错误与类似的错误:

===SORRY!=== Error while compiling /tmp/tmp.o2aoet3JrE/t.pl6
Multi method 'test' with signature :(Bar $: %foo where { ... }, *%_) must be implemented by Bar because it is required by a role
at /tmp/tmp.o2aoet3JrE/t.pl6:5
Run Code Online (Sandbox Code Playgroud)

我的问题变成:如何where在Perl 6中使用包含子句的抽象多方法?

rai*_*iph 8

TL; DR这里的问题涉及where条款和P6 的基本性质.Rakudo的错误消息是LTA.您可以使用where子句,但您必须更改使用它们的方式.

where 条款和单独的模块汇编

P6编译模块.它不会在模块中存储源代码,包括where子句的源代码.因此,当比较where用户源文件中use包含该模块rolewhere子句时,该角色中的子句与该角色中的子句相比,它不能知道它们是相同的,因此它保守地决定它不能接受它.

LTA错误消息

如上所述,编译器不会将源代码存储在已编译的模块中.所以这就是它展示的原因where { ... }.

它可以做的是where在编译角色时产生一个很棒的"你不能这样做",也许是使用实际的子句源代码,而不是在编译执行角色的类时等待不可避免地失败.

我搜索了rt.perl.org和github rakudo repo问题并且没有找到相应的票证.所以我打开了#2146.

P6的标称类型调度支持使用subsets

例行调度主要由名义(命名)类型驱动.

通过声明subset你可以给一个where约束一个名称然后你可以插入签名,从而使日常调度可以按你的意愿做:

subset Nominal-Type where *.key eq 'bar';
role Foo { multi method test(Nominal-Type $baz) { … } }
class Bar does Foo { multi method test(Nominal-Type $baz) { "Implementation" } }

Bar.new.test: my Nominal-Type $baz = :bar
Run Code Online (Sandbox Code Playgroud)

子集和符号

重要的是要注意,存在一个老化的错误,这意味着子集不能与使用显式复合符号(%@)声明的变量一起使用.

所以你必须使用标量印记$或削减印记.

此要求既适用于您在角色中编写的抽象方法的签名,也适用于用户编写的具体方法的签名.

复合子集

可以编写复合子集.上面的例子是一个标量子集,但你可以写,说:

subset Nominal-Type-Hash of Hash where *<bar>;
role Foo { multi method test(Nominal-Type-Hash \qux) { … } }
class Bar does Foo { multi method test(Nominal-Type-Hash \qux) { "Implementation" } }

Bar.new.test: my %baz := my Nominal-Type-Hash \qux = { :bar }
Run Code Online (Sandbox Code Playgroud)

请注意,我已经切换到使用子集类型在声明中削减sigil.这是因为使用您的子集的人可能希望使用sigil绑定到新变量,就像我在最后一行中所做的那样,并且他们可能在他们的方法体中.

削减sigil而不是使用$确保sigil'd别名明显不同.例如,$当用户打算编写%sigil'd版本时,用户不会意外地写出变量名的sigil'd版本.更改名称以获得额外的安全点:

class Bar does Foo { multi method test(Nominal-Type-Hash \qux) { my %baz := qux; # use %baz from here on... } }
Run Code Online (Sandbox Code Playgroud)

用例的子集

因此,您可以对现有的名义类型进行子集化,以创建一个与现有类型具有不同名称的新名义类型,并且您可以(通常会)where向该新类型添加子句:

subset PTA-BN of Pod::Block::Named where *.name eq "NAME"
Run Code Online (Sandbox Code Playgroud)

现在代码可以使用PTA-BN(或您选择的任何名称)作为参数类型约束.(我认为,对于您的用户来说,除了复制where子句之外,它会更简单,更不容易出错.)

导出子集

根据我们在下面的评论中的讨论,您需要添加一个is export子集:

subset PTA-BN is export of Pod::Block::Named where ...
Run Code Online (Sandbox Code Playgroud)

以及这里sub EXPORT { { PTA-BN => PTA-BN } }解释的自定义EXPORT例程().

子集的子集

您可以构建子集子集等子集:

subset PTA-BN2 of PTA-BN where some-other-condition;
Run Code Online (Sandbox Code Playgroud)

这将确保不仅是基本类型在运行时的价值Pod::Block::Named 它的名字"NAME",但认为some-other-condition是真实的了.

我提到这主要是作为一个很好的前奏......

用户定义的where子句

虽然例行调度主要由名义(命名)类型驱动,但需要其余的答案,有一个例外,它实际上涉及where条款!

例程调度关注where参数的一个条款,因为任何这样的子句被认为比没有条款的epsilon(一点点)更窄.

在原始代码中,角色和类方法的相应参数都有一个where子句,因此不适用.cf Signature Introspection.

但是这个功能可以在您的用例中实现一个小小的转折.实现方法可以将您的角色在参数左侧提供的子集与where用户在右侧写入的子句组合在一起,它们将在匹配时得到dib:

use Your::Module;
class User's-Class does your-role;
multi method render (PTA-BN $pod where foo --> Str) { ... } # first dibs
multi method render (PTA-BN $pod where bar --> Str) { ... } # second dibs
multi method render (PTA-BN $pod --> Str) { ... } # default
Run Code Online (Sandbox Code Playgroud)

  • 嗨@raiph.您的解决方案对我有用,您的广泛答案确实帮了很多忙.一旦我解决了问题,我就接受了你的回答.我要感谢你们在SO和reddit主题中的贡献,因为你似乎总是更加努力地帮助别人.我之前从未使用过`EXPORT`,所以我必须阅读一下,但它现在正在为'Pod :: To :: Anything`和`Pod :: To :: Man`工作.再次,非常感谢! (3认同)
  • 如果我在`unit role Pod :: To :: Anything;`代码之上添加`subset`代码,我会从试图使用它的模块中获得`无效的typename'Pod :: To :: Anything'.行调用`单元类Pod :: To :: Man做Pod :: To :: Anything;`).如果我在该行之后放置`subset`,我得到`不能在角色里面声明我们的作用域子集. (2认同)