Pod::To::Anything
,以便创建一个简单的界面来创建Perl 6 Pod格式化程序.我正在将渲染分成多个渲染方法,每个渲染方法都必须处理
the Pod specification的给定部分.
为了确保完成基于此类的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
这是LTA,Perl 6隐藏了where部分的实际内容,但这不是我的主要问题.我遇到的问题是它告诉我我实现的方法没有实现.
我把它打了一小段以确保这不是我当前代码库的特定问题:
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子句的抽象多方法?
解决方法
where子句和单独的模块编译
P6编译模块.它不会在模块中存储源代码,包括where子句的源代码.因此,当比较使用包含角色的模块的用户源文件中的where子句与角色中的where子句时,它不能知道它们是相同的,所以它保守地决定它不能接受它.
LTA错误消息
如上所述,编译器不会将源代码存储在已编译的模块中.这就是为什么它显示{…}的原因.
它可以做的是产生一个很棒的“你不能这样做”,也许是使用实际的where子句源代码,在编译角色时,而不是在编译执行角色的类时等待不可避免地失败.
我搜索了rt.perl.org和github rakudo repo问题并且没有找到相应的票证.所以我已经开了#2146.
P6的标称类型调度支持使用子集
例行调度主要由名义(命名)类型驱动.
通过声明一个子集,您可以为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表示子集不适用于使用显式复合符号(%和@)声明的变量.
所以你必须使用标量符号$或削减sigil.
此要求既适用于您在角色中编写的抽象方法的签名,也适用于用户编写的具体方法的签名.
复合子集
您可以编写复合子集.上面的例子是一个标量子集,但你可以写,比如说:
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 }
请注意,我已经切换到使用子集类型在声明中削减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... } }
用例的子集
因此,您可以对现有的名义类型进行子集化,以创建一个与现有类型具有不同名称的新名义类型,并且您可以(通常会)将where子句添加到该新类型:
subset PTA-BN of Pod::Block::Named where *.name eq "NAME"
现在代码可以使用PTA-BN(或您选择的任何名称)作为参数类型约束. (我认为对于你的用户而言,比复制where子句更简单,更不容易出错.)
导出子集
subset PTA-BN is export of Pod::Block::Named where ...
以及here解释的自定义EXPORT例程(子EXPORT {{PTA-BN => PTA-BN}}).
子集的子集
您可以构建子集子集等子集:
subset PTA-BN2 of PTA-BN where some-other-condition;
这将确保不仅是运行时Pod :: Block :: Named的值的基础类型及其名称“NAME”,而且还确保某些其他条件也是如此.
我提到这主要是作为一个很好的前奏…
用户定义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