Perl6:如何从Int派生自己的区分类型?

时间:2017-06-01 09:45:12

标签: types user-defined-types perl6 static-typing

我想在Perl 6中定义两种数据类型,这些数据类型源自Int,但同时与Int或彼此不兼容。

例如:

  • Distance派生自Int,范围0最高为32000,
  • Offset派生自Int,范围从-32000到32000

现在我希望类型DistanceOffsetInt在默认情况下可以区分并且彼此不兼容。

所以(伪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

等等!如果我试图隐含地混合DistanceOffset,我希望Perl 6编译器抱怨。

到目前为止我读过的书中没有提示如何实现这一目标。问Google几天也没有给我任何答案,这是否可能,如果可能,怎么样?

我发现了subset,但这只对某个类型设置了一些限制,但不会使其与原始类型不兼容。此外,如果在原始类型及其子集中都满足其限制,则它与原始类型无法区分。

所以我想在这里询问是否有人知道Perl 6中是否可以这样做?如果是的话,我该怎么办呢?

2 个答案:

答案 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

无论您希望DistanceOffset使用哪种功能,都必须将这些功能委托给$!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代码的初始运行结果为:

===抱歉!=== ...调用myprint(距离)永远不会有效......

前导===SORRY!===表示&#34;编译时&#34; 1 错误。

但如果我们将失败的行注释掉并再试一次,我们会得到:

类型检查在分配给$ d2 ...

时失败

此错误消息是&#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;。我在哪里吓唬报价运行时/编译时,我的意思是参考第二个视角。