签名为常量时无法解析 [英] Signature can't be resolved when it's aliased to a constant

查看:68
本文介绍了签名为常量时无法解析的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为 这个关于在单个程序中使用不同 API 的问题的后续Liz Mattijsen 建议使用常量.现在有一个不同的用例:让我们尝试创建一个 multi按 API 版本区分,如下所示:

class WithApi:ver<0.0.1>:auth:api<1>{}我的常量二 = 我的类 WithApi:ver<0.0.1>:auth:api<2>{}multi sub get-api( WithApi $foo where .^api() == 1 ) {返回那是版本 1";}multi sub get-api( WithApi $foo where .^api() == 2 ) {return "那是版本 deuce";}说 get-api(WithApi.new);说两个.new.^api;说 get-api(two.new);

我们对第二个版本使用常量,因为两者不能在一个符号空间中一起使用.但这会产生此错误:

那是版本 12无法解析调用者 get-api(WithApi.new);这些签名都不匹配:(WithApi $foo where { ... })(WithApi $foo where { ... })在块<单元>中在 ./version-signature.p6 第 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 的解决方案中,谓词 成本高昂,这让事情变得更糟.总而言之,在使用 multiple 分派的最坏情况下,开销是 allsum whereall 和 multi 中使用的 code> 子句.

  • 在代码 where .^api() == 1 &&.^name eq "WithApi",对于每个 multi 变体,43 个字符中的 42 个是重复的.相比之下,非where 子句类型约束要短得多,并且不会掩盖差异.当然,JJ 可以声明 subsets 具有类似的效果,但这会消除其解决方案的唯一优势,而不会修复其最显着的弱点.

附加编译时元数据;在多次分派中使用它

在具体解决 JJ 的问题之前,这里有一些通用技术的变体:

role Fruit {} # 声明元数据 `Fruit`我的 $vegetable-A = '白菜';我的 $vegetable-B = 'tomato' 做水果;# 将元数据附加到值multi pick (Fruit $produce) { $produce } # 基于元数据的调度说pick $vegetable-B;# 番茄

同样的,但参数化了:

enum 字段 <数学英语;role Teacher[Field] {} # 声明可参数化的元数据 `Teacher`my $Ms-England = 'Ms England';我的 $Mr-Matthews = 'Mr Matthews';$Ms-England do Teacher[Math];$Mr-Matthews 做老师[英文];多领域 (Teacher[Math]) { Math }多领域 (Teacher[English]) { English }说字段 $Mr-Matthews;# 英语

我使用了 role 作为元数据,但这是偶然的.重点是拥有可以在编译时附加的元数据,并且具有类型名称,以便可以在编译时建立分派解析候选.

JJ 运行时答案的编译时元数据版本

解决方案是声明元数据并将其附加到 JJ 的类中.

Brad 解决方案的变体:

class WithApi1 {}类 WithApi2 {}常量一 = 匿名类 WithApi:ver<0.0.1>:auth:api<1>是 WithApi1 {}常量二 = 匿名类 WithApi:ver<0.0.1>:auth:api<2>是 WithApi2 {}常量三 = 匿名类 WithApi:ver<0.0.2>:api<1>是 WithApi1 {}multi sub get-api(WithApi1 $foo) {那是 api 1"}multi sub get-api( WithApi2 $foo ) { "那是 api deuce" }说 get-api(one.new);# 那是 api 1说 get-api(two.new);# 那是 api deuce说 get-api(three.new);# 那是 api 1

另一种方法是编写单个可参数化的元数据项:

role Api[Version $] {}常量一 = 匿名类 WithApi:ver<0.0.1>:auth:api<1>Api[v1] {}常量二 = 匿名类 WithApi:ver<0.0.1>:auth:api<2>Api[v2] {}常量三 = 匿名类 WithApi:ver<0.0.2>:apiApi[v1] {}multi sub get-api( Api[v1] $foo ) { "那是 api 1" }multi sub get-api( Api[v2] $foo ) { "那是 api deuce" }说 get-api(one.new);# 那是 api 1说 get-api(two.new);# 那是 api deuce说 get-api(three.new);# 那是 api 1

版本匹配范围

JJ 在下面的评论中写道:

<块引用>

如果您使用 where 子句,您可以使用 multi 分派最多一个版本的版本(因此无需为每个版本创建一个)

此答案中涵盖的 role 解决方案还可以通过添加另一个角色来分派版本范围:

role Api[Range $ where { .min &.max ~~ 版本 }] {}...multi sub get-api( Api[v1..v3] $foo ) { "这是 api 1 到 3" }#multi sub get-api( Api[v2] $foo ) { "那是 api deuce" }

这将显示所有三个调用的这是 api 1 到 3.如果第二个 multi 被取消注释,它优先于 v2 调用.

请注意,尽管角色签名包含一个 where 子句,但仍会检查 get-api 例程调度并在编译时解析候选对象.这是因为运行角色的 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 加动态 where第 $n 天,其中 1 <= * <= 5 # 1..5

<块引用>

第一个 where 被认为是动态的,不是因为比较的性质,而是因为 Int 不是有限可枚举的.[第二个约束] ... 可以在编译时计算集合成员资格,因为它基于 Day 枚举,因此 [约束,包括 where 子句]尽管使用了 where,但仍被认为是静态的.

As a follow up to this question about using different APIs in a single program, Liz Mattijsen suggested to use constants. Now here's a different use case: let's try to create a multi that differentiates by API version, like this:

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);

We use a constant for the second version, since both can't be together in a single symbol space. But this yields this error:

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

So say two.new.^api; returns the correct api version, the caller is get-api(WithApi.new), so $foo has the correct type and the correct API version, yet the multi is not called? Is there something I'm missing here?

解决方案

TL;DR JJ's answer is a run-time where clause that calls a pair of methods on the argument of concern. Everyone else's answers do the same job, but using compile-time constructs that provide better checking and much better performance. This answer blends my take with Liz's and Brad's.

Key strengths and weaknesses of JJ's answer

In JJ's answer, all the logic is self-contained within a where clause. This is its sole strength relative to the solution in everyone else's answers; it adds no LoC at all.

JJ's solution comes with two significant weaknesses:

  • Checking and dispatch overhead for a where clause on a parameter is incurred at run-time1. This is costly, even if the predicate isn't. In JJ's solution the predicates are costly ones, making matters even worse. And to cap it all off, the overhead in the worse case when using multiple dispatch is the sum of all the where clauses used in all the multis.

  • In the code where .^api() == 1 && .^name eq "WithApi", 42 of the 43 characters are duplicated for each multi variant. In contrast a non-where clause type constraint is much shorter and would not bury the difference. Of course, JJ could declare subsets to have a similar effect, but then that would eliminate the sole strength of their solution without fixing its most significant weakness.

Attaching compile-time metadata; using it in multiple dispatch

Before getting to JJ's problem in particular, here are a couple variations on the general technique:

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

Same again, but parameterized:

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

I used a role to serve as the metadata, but that's incidental. The point was to have metadata that can be attached at compile-time, and which has a type name so dispatch resolution candidates can be established at compile-time.

A compile-time metadata version of JJ's run-time answer

The solution is to declare metadata and attach it to JJ's classes as appropriate.

A variation on Brad's solution:

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

An alternative is to write a single parameterizable metadata item:

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

Matching ranges of versions

In a comment below JJ wrote:

If you use where clauses you can have multis that dispatch on versions up to a number (so no need to create one for every version)

The role solution covered in this answer can also dispatch on version ranges by adding another 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" }

This displays That's api 1 thru 3 for all three calls. If the second multi is uncommented it takes precedence for v2 calls.

Note that the get-api routine dispatch is still checked and candidate resolved at compile-time despite the fact the role signature includes a where clause. This is because the run-time for running the role's where clause is during compilation of the get-api routine; when the get-api routine is called the role's where clause is no longer relevant.

Footnotes

1 In Multiple Constraints, Larry wrote:

For 6.0.0 ... any structure type information inferable from the where clause will be ignored [at compile-time]

But for the future he conjectured:

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

The first where is considered dynamic not because of the nature of the comparisons but because Int is not finitely enumerable. [The second constraint] ... can calculate the set membership at compile time because it is based on the Day enum, and hence [the constraint, including the where clause] is considered static despite the use of a where.

这篇关于签名为常量时无法解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆