我想在Perl 6中定义两种数据类型,这些数据类型源自Int
,但同时与Int
或彼此不兼容。
例如:
Distance
派生自Int
,范围0最高为32000,Offset
派生自Int
,范围从-32000到32000 现在我希望类型Distance
,Offset
和Int
在默认情况下可以区分并且彼此不兼容。
所以(伪Perl 6):
my Distance $d = Distance(12); // ok
my Offset $o = Offset(-1); // ok
my Distance $d2 = $o; // BUMMER!
sub myprint(Int $i) { say $i }
say $d + $o; // BUMMER!
myprint $d; // BUMMER!
myprint Int($d); // ok
等等!如果我试图隐含地混合Distance
和Offset
,我希望Perl 6编译器抱怨。
到目前为止我读过的书中没有提示如何实现这一目标。问Google几天也没有给我任何答案,这是否可能,如果可能,怎么样?
我发现了subset
,但这只对某个类型设置了一些限制,但不会使其与原始类型不兼容。此外,如果在原始类型及其子集中都满足其限制,则它与原始类型无法区分。
所以我想在这里询问是否有人知道Perl 6中是否可以这样做?如果是的话,我该怎么办呢?
答案 0 :(得分:3)
如果您确实希望它们在默认情况下具有可区分性和不兼容性,那么只需将它们完全分开即可。你可以定义你想要的任何能力。如果你使用一个'has a'关系与一个整数(而不是'是'关系),很容易将功能委托给该值(在这个例子中,我委托.Int
所以你的例子将起作用):
class Distance
{
has Int $!value handles<Int>;
method new($value where 0 <= * <= 32000) { self.bless(:$value) }
submethod BUILD(:$!value) {}
}
class Offset
{
has Int $!value handles<Int>;
method new($value where -32000 <= * <= 32000) { self.bless(:$value) }
submethod BUILD(:$!value) {}
}
my Distance $d = Distance.new(12); # ok
my Offset $o = Offset.new(-1); # ok
my Distance $d2 = $o; # Bummer! Type check fail
sub myprint(Int $i) { say $i }
say $d + $o; # Bummer!, can't add those objects
myprint $d; # Bummer!, $d isn't an Int, can't print
myprint Int($d); # ok, prints 12, converting with Int
无论您希望Distance
和Offset
使用哪种功能,都必须将这些功能委托给$!value
以便轻松实现。
编辑:如果你真的想要你想要的语法my Distance $d = Distance(12);
,可以在Int
类中添加一个方法来调用你的构造函数:
Int.^add_method('Distance', method () { Distance.new(self) });
Int.^compose;
我实际上不会建议这一点 - 可能更有说服力而不是有用。更好地鼓励标准构造函数的使用。 @raiph还指出了惯用的Perl:
my Distance $d .= new(12);
答案 1 :(得分:2)
Curt's answer抓住了chi想要抓住的错误。这个补充答案是我对Chi's follow on question提出的初步探索:
为什么在编译/运行Curt的代码时,Rakudo会等到运行时报告三个错误中的两个?
在初步探索结束时,我将Curt的代码包装在BEGIN
块中。此代码出现以在编译时报告所有错误(当然,在逐条记录每个先前错误之后逐个报告)。 (Click for runnable snippet。)
这是一个稻草人的答案,为Perl 6核心开发者设置了击落。
Curt代码的初始运行结果为:
前导===SORRY!===
表示&#34;编译时&#34; 1 错误。
但如果我们将失败的行注释掉并再试一次,我们会得到:
此错误消息是&#34;运行时&#34; 1 消息 - 它不以===SORRY!===
开头。
为什么编译器要等到&#34;运行时&#34;抱怨?
答案似乎在于Perl 6的大部分动态默认性质以及失败的代码:
my Distance $d2 = $o; # Bummer! Type check fail
当编译器在&#34;编译时&#34;遇到此声明时,此行的my Distance $d2
部分已完全处理(引入新的词法范围符号$d2
)。但是这一行的=
部分是&#34;运行时&#34;操作;初始化分配和相应的类型检查发生在&#34;运行时&#34;。
但是开发人员可能希望强制进行类型检查,从而在编译时发生类型检查错误。现在怎么样?
BEGIN
时间 Perl 6通过phasers支持程序执行空间/时间旅行。第一个移相器是BEGIN
移相器,它在编译时尽快运行&#34;并且可以像这样使用:
BEGIN my Distance $d2 = $o;
如果您使用上述更改重新编译,则错误现在出现在编译时 1 :
===SORRY!=== Error while compiling...
An exception occurred while evaluating a BEGIN...
Type check failed in assignment to $d2...
如果我们现在注释掉最新的失败行,请再试一次,我们得到:
没有领先===SORRY!===
所以这又是一个&#34;运行时&#34;错误。
将失败的行更改为:
重新运行代码BEGIN say $d + $o;
的产率:
0
12
在stdout上,在stderr上我们得到:
Use of uninitialized value of type Distance in numeric context...
Use of uninitialized value of type Offset in numeric context...
Uhoh。这不仅没有编译时错误,也没有运行时错误! (运行时警告可能会让游戏远离0
。因为声明my...
和$d
的{{1}}行没有$o
的前缀,符号尚未在编译时初始化,这是BEGIN
行运行的时间。但所有这些都没有实际意义;我们已经明显向后退了一步。)
如果我们在单个BEGIN say $d + $o;
块中包含所有的Curt代码会怎样?
BEGIN
BEGIN { ... Curt's code goes here ... }
宾果!现在所有错误都显示在Curt的原始代码中,但报告似乎是在编译时发生的(在逐条记录每个先前的错误之后逐一发生)。
1 我引用了很多引用&#34;编译时间&#34;和&#34;运行时&#34;因为它们在Perl 6中具有含糊不清的含义.Perl 6允许用户代码执行任何操作,包括在运行时运行编译器,并允许用户代码在编译期间执行任何操作,包括运行时操作。所以从一个角度来看,在编译时阶段可以有一个或多个运行时阶段,反之亦然。但从第二个角度来看, 编译时阶段,即当你在开发会话期间坐在那里并且你只是运行编译器时。同样地, 运行时阶段,例如,当您的代码在生产&#34;中运行时#34;。我在哪里不吓唬报价运行时/编译时,我的意思是参考第二个视角。