raku - 别名为常量时无法解析签名
问题描述
作为这个关于在单个程序中使用不同 API 的问题的后续,Liz Mattijsen 建议使用常量。现在这是一个不同的用例:让我们尝试创建一个multi
按 API 版本区分的,如下所示:
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
multi sub get-api( WithApi $foo where .^api() == 1 ) {
return "That's version 1";
}
multi sub get-api( WithApi $foo where .^api() == 2 ) {
return "That's version deuce";
}
say get-api(WithApi.new);
say two.new.^api;
say get-api(two.new);
我们在第二个版本中使用了一个常量,因为两者不能一起在一个符号空间中。但这会产生这个错误:
That's version 1
2
Cannot resolve caller get-api(WithApi.new); none of these signatures match:
(WithApi $foo where { ... })
(WithApi $foo where { ... })
in block <unit> at ./version-signature.p6 line 18
所以say two.new.^api;
返回正确的api版本,调用者是get-api(WithApi.new)
,所以$foo
有正确的类型和正确的API版本,但是multi没有被调用?我在这里缺少什么吗?
解决方案
TL;DR JJ 的答案是一个运行时where
子句,它在关注的参数上调用一对方法。其他人的答案都做同样的工作,但使用提供更好检查和更好性能的编译时构造。这个答案将我的观点与 Liz 和 Brad 的观点融为一体。
JJ 答案的主要优点和缺点
在 JJ 的回答中,所有逻辑都包含在一个where
子句中。相对于其他所有人的答案,这是它相对于解决方案的唯一优势;它根本没有添加任何 LoC。
JJ 的解决方案有两个明显的弱点:
检查和调度
where
参数子句的开销发生在运行时1。即使谓词不是,这也是代价高昂的。在 JJ 的解决方案中,谓词是昂贵的,使事情变得更糟。总而言之,使用多重分派时的最坏情况下的开销是所有s中使用的所有子句的总和。where
multi
在代码中,每个变体
where .^api() == 1 && .^name eq "WithApi"
的 43 个字符中有 42 个重复。multi
相比之下,非where
子句类型约束要短得多,并且不会掩盖差异。当然,JJ 可以声明subset
s 具有类似的效果,但这会消除他们解决方案的唯一优势,而不会修复其最重要的弱点。
附加编译时元数据;在多次调度中使用它
在特别讨论 JJ 的问题之前,这里有一些通用技术的变体:
role Fruit {} # Declare metadata `Fruit`
my $vegetable-A = 'cabbage';
my $vegetable-B = 'tomato' does Fruit; # Attach metadata to a value
multi pick (Fruit $produce) { $produce } # Dispatch based on metadata
say pick $vegetable-B; # tomato
再次相同,但参数化:
enum Field < Math English > ;
role Teacher[Field] {} # Declare parameterizable metadata `Teacher`
my $Ms-England = 'Ms England';
my $Mr-Matthews = 'Mr Matthews';
$Ms-England does Teacher[Math];
$Mr-Matthews does Teacher[English];
multi field (Teacher[Math]) { Math }
multi field (Teacher[English]) { English }
say field $Mr-Matthews; # English
我用 arole
作为元数据,但那是偶然的。关键是要有可以在编译时附加的元数据,并且它有一个类型名称,以便可以在编译时建立调度解决方案候选者。
JJ 运行时答案的编译时元数据版本
解决方案是声明元数据并将其附加到 JJ 的类中。
布拉德解决方案的变体:
class WithApi1 {}
class WithApi2 {}
constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> is WithApi1 {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> is WithApi2 {}
constant three = anon class WithApi:ver<0.0.2>:api<1> is WithApi1 {}
multi sub get-api( WithApi1 $foo ) { "That's api 1" }
multi sub get-api( WithApi2 $foo ) { "That's api deuce" }
say get-api(one.new); # That's api 1
say get-api(two.new); # That's api deuce
say get-api(three.new); # That's api 1
另一种方法是编写一个可参数化的元数据项:
role Api[Version $] {}
constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> does Api[v1] {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> does Api[v2] {}
constant three = anon class WithApi:ver<0.0.2>:api<v1> does Api[v1] {}
multi sub get-api( Api[v1] $foo ) { "That's api 1" }
multi sub get-api( Api[v2] $foo ) { "That's api deuce" }
say get-api(one.new); # That's api 1
say get-api(two.new); # That's api deuce
say get-api(three.new); # That's api 1
版本匹配范围
在下面的评论中,JJ 写道:
如果您使用
where
子句,您可以让multi
s 在最多多个版本上分派(因此无需为每个版本创建一个)
此答案中涵盖的role
解决方案还可以通过添加另一个角色来分派版本范围:
role Api[Range $ where { .min & .max ~~ Version }] {}
...
multi sub get-api( Api[v1..v3] $foo ) { "That's api 1 thru 3" }
#multi sub get-api( Api[v2] $foo ) { "That's api deuce" }
这将显示That's api 1 thru 3
所有三个呼叫。v2
如果第二个 multi 未注释,则调用优先。
请注意,get-api
尽管角色签名包含一个where
子句,但仍会检查例程调度并在编译时解析候选者。这是因为运行角色where
子句的运行时间是在get-api
程序编译期间;当get-api
例程被调用时,角色的where
子句不再相关。
脚注
1在多重约束中,拉里写道:
对于 6.0.0 ...从
where
子句推断出的任何结构类型信息都将被忽略 [在编译时]
但对于未来,他猜想:
my enum Day ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
Int $n where 1 <= * <= 5 # Int plus dynamic where
Day $n where 1 <= * <= 5 # 1..5
第一个
where
被认为是动态的,不是因为比较的性质,而是因为Int
不是有限可枚举的。[第二个约束] ...可以在编译时计算集合成员资格,因为它基于Day
枚举,因此 [约束,包括where
子句] 被认为是静态的,尽管使用了where
.
推荐阅读
- php - PHP:将对象转换为以对象属性为键的关联数组
- python - Elastic Beanstalk is not installing requirements.txt file
- javascript - 启动反应应用程序时出错
- jenkins - 通过 jenkins 从 kafka 队列中检索消息数
- javascript - 失败:无法读取未定义的属性“querySelector” - 业力茉莉
- clion - 如何在 CLion 2018.2 中切换工具链?
- javascript - 设置 width : 0 不隐藏文本
- excel - 使用单元格值过滤日期之间的数据透视表
- c# - 我正在尝试在 C# 中创建一个插件来验证 mailItem.Body 以避免特定内容,但是当我发送消息时我的事件没有激活
- jquery - 首次打开后引导模式不起作用