别名为常量时无法解析签名
Signature can't be resolved when it's aliased to a constant
作为 , . Now here's a different use case: let's try to create a 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);
我们对第二个版本使用常量,因为两者不能一起出现在一个符号中 space。但这会产生这个错误:
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;
returns 正确的 api 版本,调用者是 get-api(WithApi.new)
,所以 $foo
有正确的类型和正确的 API 版本,却没有调用 multi?我在这里遗漏了什么吗?
解决方案非常简单:也为“1”版本设置别名:
my constant one = my 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(one $foo) {
return "That's version 1";
}
multi sub get-api(two $foo) {
return "That's version deuce";
}
say one.new.^api; # 1
say get-api(one.new); # That's version 1
say two.new.^api; # 2
say get-api(two.new); # That's version deuce
这还允许您删除签名中的 where
子句。
请注意,您无法通过他们的名字来区分他们:
say one.^name; # WithApi
say two.^name; # WithApi
如果您希望能够做到这一点,则必须设置与 class 关联的元对象的名称:
my constant one = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
BEGIN one.^set_name("one");
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
BEGIN two.^set_name("two");
那你就可以通过名字来区分了:
say one.^name; # one
say two.^name; # two
伊丽莎白·马蒂森。签名匹配符号,而不是符号名称。但是,当您使用别名(使用常量)一个新名称时,您仍然保留该名称。让我们用它来进行统一的多重调用,其中唯一改变的是 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> {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
my constant three = my class WithApi:ver<0.0.2>:api<1> {}
multi sub get-api( $foo where .^api() == 1 && .^name eq "WithApi" ) {
return "That's version 1";
}
multi sub get-api( $foo where .^api() == 2 && .^name eq "WithApi") {
return "That's version deuce";
}
say get-api(WithApi.new); # That's version 1
say get-api(two.new); # That's version deuce
say get-api(three.new); # # That's version 1
再次遵循 Elizabeth 在 中的回答,常量用于新版本以避免命名空间冲突,但 multis 将仅基于 api 版本以相对类型安全的方式选择,无需在签名中使用别名符号。即使您发明了一个新常量来使用任何元数据对 WithApi 进行别名,multi 仍将根据 api 版本(这正是我一直在寻找的)进行选择。
TL;DR 是一个 运行-time where
子句,它在关注的参数上调用一对方法。其他人的答案做同样的工作,但使用编译时结构提供更好的检查和更好的性能。这个答案融合了我与 Liz 和 Brad 的看法。
JJ 回答的主要优点和缺点
在 JJ 的回答中,所有逻辑都包含在 where
子句中。相对于其他人的答案中的解决方案,这是它的唯一优势;它根本不添加 LoC。
JJ 的解决方案有两个明显的弱点:
参数上 where
子句的检查和分派开销发生在 运行-time1。这是昂贵的,即使谓词不是。在 JJ 的解决方案中,谓词 是 昂贵的谓词,使事情变得更糟。总而言之,在使用 multiple dispatch 的最坏情况下,开销是 sum of all where
子句用于 all multi
s.
在代码 where .^api() == 1 && .^name eq "WithApi"
中,每个 multi
变体的 43 个字符中有 42 个是重复的。相比之下,非 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
我使用 role
作为元数据,但这是偶然的。关键是要有可以在编译时附加的元数据,它有一个类型名称,因此可以在编译时建立分派解决方案候选者。
JJ 运行-time answer
的编译时元数据版本
解决方案是声明元数据并根据需要将其附加到 JJ 的 类。
Brad 解决方案的变体:
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 在下面的评论中写道:
If you use where
clauses you can have multi
s that dispatch on versions up to a number (so no need to create one for every version)
此答案中涵盖的 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
。如果第二个 multi 未注释,则优先调用 v2
。
请注意,尽管角色签名包含 where
子句,但仍会在编译时检查 get-api
例程分派和候选者解析。这是因为 运行 角色的 where
子句的 运行 时间是在编译 get-api
例程期间;当 get-api
例程被 调用时, 角色的 where
子句不再相关。
脚注
1 在 Multiple Constraints 中,拉里写道:
For 6.0.0 ... any structure type information inferable from the where
clause will be ignored [at compile-time]
但对于未来他推测:
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
.
在给定的命名空间中只能有一个东西。
我假设你将第二个声明放入常量并用 my
声明它的全部原因是它给你一个重新声明错误。
问题是,它应该仍然给您一个重新声明错误。
您的代码甚至不应该编译。
您应该改为使用 anon
声明第二个。
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
那么很明显为什么您尝试做的事情不起作用。
第二个声明从一开始就不会安装到命名空间中。
因此,当您在第二个 multi
sub 中使用它时,它声明其参数与第一个 class.
的类型相同
(即使您在代码中使用 my
,也无法将其安装到命名空间中。)
您假设命名空间是平面命名空间。
不是。
您可以有一个 class 有一个名字,但只能在另一个名字下访问。
our constant Bar = anon class Foo {}
sub example ( Bar $foo ) {
say $foo.^name; # Foo
}
example( Bar );
为方便起见,Raku 将 class 安装到命名空间中。
否则会有很多代码看起来像:
our constant Baz = class Baz {}
您在尝试使用命名空间的同时又试图颠覆命名空间。
我不知道你为什么期望它起作用。
让您的确切代码像您编写的那样工作的快速方法是声明第二个 class 是第一个的子class。
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> is WithApi {}
# ^________^
然后当第二个multi
检查它的参数是第一种类型时,当你给它第二个时它仍然匹配。
这不太好。
并没有真正的内置方法来完全按照您的意愿进行操作。
您可以尝试创建一个新的元类型,它可以创建一个新类型,其行为与 classes 一样。
我个人只是将它们分别命名为独立的名称。
constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
如果您从模块加载它们:
constant one = BEGIN {
# this is contained within this block
use WithApi:ver<0.0.1>:auth<github:JJ>:api<1>;
WithApi # return the class from the block
}
constant two = BEGIN {
use WithApi:ver<0.0.1>:auth<github:JJ>:api<2>;
WithApi
}
作为 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);
我们对第二个版本使用常量,因为两者不能一起出现在一个符号中 space。但这会产生这个错误:
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;
returns 正确的 api 版本,调用者是 get-api(WithApi.new)
,所以 $foo
有正确的类型和正确的 API 版本,却没有调用 multi?我在这里遗漏了什么吗?
解决方案非常简单:也为“1”版本设置别名:
my constant one = my 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(one $foo) {
return "That's version 1";
}
multi sub get-api(two $foo) {
return "That's version deuce";
}
say one.new.^api; # 1
say get-api(one.new); # That's version 1
say two.new.^api; # 2
say get-api(two.new); # That's version deuce
这还允许您删除签名中的 where
子句。
请注意,您无法通过他们的名字来区分他们:
say one.^name; # WithApi
say two.^name; # WithApi
如果您希望能够做到这一点,则必须设置与 class 关联的元对象的名称:
my constant one = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
BEGIN one.^set_name("one");
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
BEGIN two.^set_name("two");
那你就可以通过名字来区分了:
say one.^name; # one
say two.^name; # two
伊丽莎白·马蒂森
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> {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
my constant three = my class WithApi:ver<0.0.2>:api<1> {}
multi sub get-api( $foo where .^api() == 1 && .^name eq "WithApi" ) {
return "That's version 1";
}
multi sub get-api( $foo where .^api() == 2 && .^name eq "WithApi") {
return "That's version deuce";
}
say get-api(WithApi.new); # That's version 1
say get-api(two.new); # That's version deuce
say get-api(three.new); # # That's version 1
再次遵循 Elizabeth 在
TL;DR where
子句,它在关注的参数上调用一对方法。其他人的答案做同样的工作,但使用编译时结构提供更好的检查和更好的性能。这个答案融合了我与 Liz 和 Brad 的看法。
JJ 回答的主要优点和缺点
在 JJ 的回答中,所有逻辑都包含在 where
子句中。相对于其他人的答案中的解决方案,这是它的唯一优势;它根本不添加 LoC。
JJ 的解决方案有两个明显的弱点:
参数上
where
子句的检查和分派开销发生在 运行-time1。这是昂贵的,即使谓词不是。在 JJ 的解决方案中,谓词 是 昂贵的谓词,使事情变得更糟。总而言之,在使用 multiple dispatch 的最坏情况下,开销是 sum of allwhere
子句用于 allmulti
s.在代码
where .^api() == 1 && .^name eq "WithApi"
中,每个multi
变体的 43 个字符中有 42 个是重复的。相比之下,非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
我使用 role
作为元数据,但这是偶然的。关键是要有可以在编译时附加的元数据,它有一个类型名称,因此可以在编译时建立分派解决方案候选者。
JJ 运行-time answer
的编译时元数据版本解决方案是声明元数据并根据需要将其附加到 JJ 的 类。
Brad 解决方案的变体:
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 在下面的评论中写道:
If you use
where
clauses you can havemulti
s that dispatch on versions up to a number (so no need to create one for every version)
此答案中涵盖的 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
。如果第二个 multi 未注释,则优先调用 v2
。
请注意,尽管角色签名包含 where
子句,但仍会在编译时检查 get-api
例程分派和候选者解析。这是因为 运行 角色的 where
子句的 运行 时间是在编译 get-api
例程期间;当 get-api
例程被 调用时, 角色的 where
子句不再相关。
脚注
1 在 Multiple Constraints 中,拉里写道:
For 6.0.0 ... any structure type information inferable from the
where
clause will be ignored [at compile-time]
但对于未来他推测:
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 becauseInt
is not finitely enumerable. [The second constraint] ... can calculate the set membership at compile time because it is based on theDay
enum, and hence [the constraint, including thewhere
clause] is considered static despite the use of awhere
.
在给定的命名空间中只能有一个东西。
我假设你将第二个声明放入常量并用 my
声明它的全部原因是它给你一个重新声明错误。
问题是,它应该仍然给您一个重新声明错误。 您的代码甚至不应该编译。
您应该改为使用 anon
声明第二个。
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
那么很明显为什么您尝试做的事情不起作用。
第二个声明从一开始就不会安装到命名空间中。
因此,当您在第二个 multi
sub 中使用它时,它声明其参数与第一个 class.
(即使您在代码中使用 my
,也无法将其安装到命名空间中。)
您假设命名空间是平面命名空间。
不是。
您可以有一个 class 有一个名字,但只能在另一个名字下访问。
our constant Bar = anon class Foo {}
sub example ( Bar $foo ) {
say $foo.^name; # Foo
}
example( Bar );
为方便起见,Raku 将 class 安装到命名空间中。
否则会有很多代码看起来像:
our constant Baz = class Baz {}
您在尝试使用命名空间的同时又试图颠覆命名空间。 我不知道你为什么期望它起作用。
让您的确切代码像您编写的那样工作的快速方法是声明第二个 class 是第一个的子class。
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> is WithApi {}
# ^________^
然后当第二个multi
检查它的参数是第一种类型时,当你给它第二个时它仍然匹配。
这不太好。
并没有真正的内置方法来完全按照您的意愿进行操作。
您可以尝试创建一个新的元类型,它可以创建一个新类型,其行为与 classes 一样。
我个人只是将它们分别命名为独立的名称。
constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
如果您从模块加载它们:
constant one = BEGIN {
# this is contained within this block
use WithApi:ver<0.0.1>:auth<github:JJ>:api<1>;
WithApi # return the class from the block
}
constant two = BEGIN {
use WithApi:ver<0.0.1>:auth<github:JJ>:api<2>;
WithApi
}