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

How to use abstract multi methods containing a where?

我正在开发 Perl 6 模块,Pod::To::Anything, in an effort to make an easy interface to create Perl 6 Pod formatters. I'm splitting up rendering into multiple render methods, each having to deal with a given part of the Pod specification

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

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

我试过如下实现:

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

然而,当我尝试 运行 程序时,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

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

我已经将其降低了一点以确保这不是我当前代码库的特定问题:

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

此代码出现类似错误:

===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

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

TL;DR 这里的问题与 where 子句和 P6 的基本性质有关。 Rakudo 的错误信息是LTA。您可以使用 where 子句,但您必须更改它们的使用方式。

where 子句和模块的单独编译

P6编译模块。它不存储模块中的源代码,包括 where 子句的源代码。因此,当它去比较用户源文件中 use 包含 role 的模块中的 where 子句与角色中的 where 子句时,它可以'不知道它们是一样的所以它保守地决定它不能接受它。

LTA 错误信息

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

的原因

它可以做的是在编译角色时生成一个很棒的 "you can't do that",也许带有实际的 where 子句源代码,而不是在编译 class 时等待不可避免地失败扮演角色。

我搜索了 rt.perl.org 和 github rakudo 回购问题,但没有找到相应的工单。所以我打开了 #2146.

P6的nominal type dispatch支持使用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

子集和印记

重要的是要注意 an aged bug 这意味着子集不适用于使用显式复合符号(%@)声明的变量。

所以你必须要么使用标量印记$要么斜线印记。

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

复合子集

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

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 }

请注意,我已将子集类型声明中的印记改成斜线。这是因为那些使用您的子集的人可能希望使用印记绑定到一个新变量,就像我在最后一行所做的那样,他们可能会在他们的方法体中。

削减印记而不是使用 $ 确保印记的别名明显不同。例如,当用户打算编写 % 签名版本时,用户不能意外写入变量名称的 $ 签名版本。为了额外的安全点也更改名称:

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

您的用例的子集

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

subset PTA-BN of Pod::Block::Named where *.name eq "NAME"

现在代码可以使用 PTA-BN(或您选择的任何名称)作为参数类型约束。 (我认为与复制 where 子句相比,这对您的用户来说更简单,更不容易出错。)

正在导出您的子集

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

subset PTA-BN is export of Pod::Block::Named where ...

还有自定义 EXPORT 例程 (sub EXPORT { { PTA-BN => PTA-BN } }),如 here 所述。

子集的子集

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

subset PTA-BN2 of PTA-BN where some-other-condition;

这将确保不仅 运行 时间 Pod::Block::Named 的值的基础类型是其名称 "NAME",而且some-other-condition也是真的。

我提到这主要是作为......

的一个很好的前奏

用户定义的 where 个子句

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

例程调度会注意参数上的 where 子句,因为任何此类子句都被认为比没有子句窄(一点点)。

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

但是此功能可以让您的用例发生一些小变化。实现方法可以将您的角色提供的写在参数左侧的子集与用户写在右侧的 where 子句组合起来,他们将在匹配时获得 dibs:

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