像Int(酷)这样的强制有什么意义?

时间:2016-01-19 10:47:49

标签: perl6 raku

The Perl 6 Web site on functions says

  

强制类型可以帮助您在例程中使用特定类型,但接受更广泛的输入。调用例程时,参数会自动转换为更窄的类型。

sub double(Int(Cool) $x) {
    2 * $x
}

say double '21';    # 42
say double Any;     # Type check failed in binding $x; expected 'Cool' but got 'Any'
     

这里Int是强制参数的目标类型,而Cool是例程接受作为输入的类型。

但潜艇有什么意义呢? $x只是Int吗?为什么要限制调用者为参数实现Cool

我对这个例子感到困惑,因为Int已经is Cool。所以我做了一个示例,其中类型不共享层次结构:

class Foo { method foomethod { say 'foomethod' } }
class Bar {}

class Quux is Foo {
# class Quux { # compile error
  method Bar { Bar.new }
}

sub foo(Bar(Foo) $c) {
  say $c.WHAT;    # (Bar)
  # $c.foomethod  # fails if uncommented: Method 'foomethod' not found for invocant of class 'Bar'
}

foo(Quux.new)

此处foo的邀请被限制为提供可以转换为Foo的{​​{1}}但Bar甚至无法调用foo的方法在Foo上,因为其类型 $c。那么为什么Bar会首先关注被强制类型是foo呢?

有人可以对此有所了解吗?还要感谢指向相应文档和规范部分的链接。我在那里找不到任何有用的东西。

6 个答案:

答案 0 :(得分:5)

这样做是接受一个 Cool 子类型的值,并尝试将其转换为 Int 。那时 Int ,无论以前是什么。

所以

sub double ( Int(Cool) $n ) { $n * 2 }

可以被认为是(我认为这是它在Rakudo中的实际实现方式)

# Int is a subtype of Cool otherwise it would be Any or Mu
proto sub double ( Cool $n ) {*}

# this has the interior parts that you write
multi sub double (  Int $n ) { $n * 2 }

# this is what the compiler writes for you
multi sub double ( Cool $n ) {
    # calls the other multi since it is now an Int
    samewith Int($n);
}

所以这接受任何 Int Str Rat FatRat Num Array Hash 等,并尝试将其转换为 Int ,然后再使用它调用&infix:<*>,并{ {1}}。

2

您可以将其限制为 Cool 而不是 Any ,因为所有 Cool 值基本上都需要为 Int提供强制

say double ' 5 '; # 25 say double 2.5; # 4 say double [0,0,0]; # 6 say double { a => 0, b => 0 }; # 4 可以缩短为:( Int(Any) $ )

你可能会这样做的原因是你需要它在sub中是:( Int() $ )因为你正在调用其他代码来执行不同类型的不同事情。

Int

在这种特殊情况下,你可以把它写成其中一个

sub example ( Int(Cool) $n ) returns Int {
    other-multi( $n ) * $n;
}

multi sub other-multi ( Int $ ) { 10 }
multi sub other-multi ( Any $ ) {  1 }

say example 5;   # 50
say example 4.5; # 40

它们中没有一个像使用签名强迫它一样明确。

通常对于这样一个简单的功能,你可以使用其中一个,它可能会做你想要的。

sub example ( Cool $n ) returns Int {
    other-multi( Int($n) ) * Int($n);
}

sub example ( Cool $n ) returns Int {
    my $temp = Int($n);
    other-multi( $temp ) * $temp;
}

sub example ( Cool $n is copy ) returns Int {
    $n = Int($n);
    other-multi( $n ) * $n;
}

答案 1 :(得分:5)

更新今天回顾了这个答案我得出结论我完全误解了@musiKk的所作所为。这在@darch's question和@ musiKk的回复中得到了最清楚的揭示:

  

@darch:或者你的问题是为什么人们可能更喜欢Int(酷)而不是Int(Any)?如果是这样的话,那就是要问的问题。

     

@musiKk:这正是我的问题。 :)

回顾其他许多答案,我认为没有人按我现在认为的方式解决这个问题。

我当然可能错了,所以我决定做的就是保留原来的问题,特别是将标题保留原样,并保留原来的答案,而是写一个新的答案来解决@darch的问题。改动。

指定参数类型,不强制:Int $x

我们可以声明:

sub double (Int $x) { ... } # Accept only Int. (No coercion.)

然后这会起作用:

double(42);

但遗憾的是,在回复此问题时键入42

double(prompt('')); # `prompt` returns the string the user types

导致double调用因Type check failed in binding $x; expected Int but got Str ("42")而失败,因为42虽然看起来像数字,但在技术上是Str类型的字符串,我们已经要求没有强制。

使用全面强制指定参数类型:Int() $x

我们可以在子签名中引入任何值的毯式强制:

sub double (Int(Any) $x) { ... } # Take Any value. Coerce to an Int.

或者:

sub double (Int() $x)    { ... } # Same -- `Int()` coerces from Any.

现在,如果在42语句提示时键入double(prompt(''));,则运行时类型检查失败不再适用,而是运行时尝试将字符串强制转换为Int。如果用户键入格式正确的数字,则代码才有效。如果他们输入123abc,强制将在运行时失败并显示错误消息:

Cannot convert string to number: trailing characters after number in '123⏏abc'

任何值的毯式强制的一个问题是像这样的代码:

class City { ... } # City has no Int coercion
my City $city;
double($city);

在运行时失败并显示以下消息:“找不到'City'类调用的方法'Int'。”

指定参数类型,使用来自Cool值的强制:Int(Cool) $x

我们可以选择任何价值的强制和全面强制之间的平衡点。

强制来自的最佳类通常是Cool类,因为保证Cool值可以很好地强制转换为其他基本类型或生成一个很好的错误消息:

# Accept argument of type Cool or a subclass and coerce to Int:
sub double (Int(Cool) $x) { ... }

使用此定义,以下内容:

double(42);
double(prompt(''));

尽可能地工作,并且:

double($city);

失败,“类型检查失败,绑定$ x;预期酷但得到城市(城市)”,对于程序员来说,可以说比“方法'Int'找不到类'城''这样的”更好的诊断。“

  

为什么foo会首先关注那个被胁迫的类型是Foo?

希望现在显而易见的是,值得将强制从类型限制为Foo的唯一原因是因为这是一种预期成功强制转换为Bar值的类型(或者,可能是失败的一个友好的消息)。

  

有人可以对此有所了解吗?还要感谢指向相应文档和规范部分的链接。我在那里找不到任何有用的东西。

您最初引用的文档几乎都是针对最终用户文档的。希望它现在有意义,你已经准备好了。如果没有,请评论,我们将从那里开始。

答案 2 :(得分:2)

我错过了什么吗?我不是Perl 6专家,但似乎语法允许人们单独指定允许哪些输入类型如何将输入呈现给函数

限制允许的输入很有用,因为这意味着当使用无意义的参数调用函数时,代码将导致错误,而不是静默(无用)类型转换。

我不认为两种类型不是等级关系的例子是有道理的。

答案 3 :(得分:2)

comments on the original question,@ musiKk的问题“像Int(Cool)这样的强制点有什么意义?”的更好形式。原来是:

  

为什么人们会更喜欢Int(Cool)而不是Int(Any)

一个必然的结论是:

  

为什么人们会更喜欢Int(Any)而不是Int(Cool)

首先,列出各种相关选项:

sub _Int_strong (Int       $) {} # Argument must be Int
sub _Int_cool   (Int(Cool) $) {} # Argument must be Cool; Int invoked
sub _Int_weak   (Int(Any)  $) {} # Argument must be Any;  Int invoked
sub _Int_weak2  (Int()     $) {} # same
sub _Any        (Any       $) {} # Argument must be Any
sub _Any2       (          $) {} # same
sub _Mu         (Mu        $) {} # Weakest typing - just memory safe (Mu)

_Int_strong val; # Fails to bind if val is not an Int
_Int_cool   val; # Fails to bind if val is not Cool. Int invoked.
_Int_weak   val; # Fails to bind if val is not Any.  Int invoked.
_Any        val; # Fails to bind if val is Mu
_Mu         val; # Will always bind. If val is a native value, boxes it.

为什么人们会更喜欢Int(Cool)而不是Int(Any)

因为Int(Cool)的键入略强。参数必须是Cool类型,而不是更广泛的Any和:

  • 静态分析将拒绝绑定代码,以将非Cool的参数传递给例程,该例程的相应参数具有类型约束Int(Cool)。如果静态分析显示没有其他常规候选对象能够接受该调用,则编译器将在编译时拒绝该调用。这是此答案最后一节中解释的“强类型输入”的含义之一。

  • 如果值 Cool,那么可以保证它具有行为良好的.Int转换方法。因此,它在运行时不会产生Method not found错误,并且如果无法产生转换为整数值,可以依靠它来提供良好的错误消息。

为什么人们会更喜欢Int(Any)而不是Int(Cool)

由于Int(Any)的键入略弱,因为该参数可以是任何常规类型,而P6只会尝试使其起作用:

  • .Int将在传递给例程的参数上调用,该例程的相应参数具有类型约束Int(...),无论...是什么。如果传递的参数具有一个.Int方法,则调用和后续转换都有机会成功。

  • 如果.Int失败,则错误消息将是.Int方法产生的错误消息。如果参数实际上是Cool,那么如果.Int方法无法转换为Int,则会产生一个好的错误消息。否则,.Int方法可能不是内置方法,结果将是运气不好。

为什么首先Foo(Bar)

关于弱打字和强打字到底是怎么回事?

对函数参数的Int(...)约束将导致以下任一情况:

  • 无法输入检查;或

  • 对相应参数进行.Int转换,将其强制转换为整数值-或失败,使相应参数包含Failure

使用写此答案时(2019)时的Wikipedia定义,此类型检查和尝试的转换将是:

  • strong typing,在某种意义上,像Int(...)这样的类型约束是“使用编程语言类型,以便既捕获代码的不变性,并确保其正确性,并且明确排除某些编程错误类别”;

  • 当前Rakudo中的弱类型输入是指Rakudo不会在编译时检查...中的Int(...),即使理论上可以。也就是说,sub double (Int $x) {}; double Date;产生编译时间错误(Calling double(Date) will never work),而sub double (Int(Cool) $x) {}; double Date;产生运行时间错误({{1} })。

  • type conversion;

  • weak typing,在某种意义上是implicit type conversion,在某种意义上,编译器将在执行调用的过程中处理Type check failed in binding强制;

  • explicit type conversion,是指.Int约束明确指示编译器将其转换为绑定调用的一部分;

  • 已检查显式类型转换-P6仅键入安全的转换/强制。

答案 4 :(得分:1)

我相信答案很简单,即使您将其视为Int,也可能不希望将参数限制为Int。出于某种原因,你希望能够将一个数组乘以一个哈希,但是如果args不能被视为Int(即不是Cool),则会失败。

my @a = 1,2,3;
my %h = 'a' => 1, 'b' => 2;
say @a.Int; # 3 (List types coerced to the equivalent of .elems when treated as Int)
say %h.Int; # 2

sub m1(Int $x, Int $y) {return $x * $y}
say m1(3,2); # 6
say m1(@a,%h); # does not match

sub m2(Int(Cool) $x, Int(Cool) $y) {return $x * $y}
say m2('3',2); # 6
say m2(@a,%h); # 6
say m2('foo',2); # does not match

当然,您也可以在没有签名的情况下执行此操作,因为数学运算会自动强制转换类型:

sub m3($x,$y) {return $x * $y}
say m3(@a,%h); # 6

但是,这会将您的类型检查推迟到子内部,这会破坏签名的目的并阻止您将子类型设为multi

答案 5 :(得分:1)

Cool的所有子类型(正如Cool要求的那样)将被强制转换为Int。因此,如果您的sub内部的运算符或例程仅与Int参数一起使用,则您不必添加额外的语句/表达式转换为Int也不需要该运算符/例程的代码考虑Cool的其他子类型。无论你在哪里使用它,它都会强制参数在你的子内部Int

你的例子是倒退的:

class Foo { method foomethod { say 'foomethod' } }
class Bar {}

class Quux is Bar {
  method Foo { Foo.new }
}

sub foo(Foo(Bar) $c) {
#= converts $c of type Bar to type Foo
#= returns result of foomethod
  say $c.WHAT;    #-> (Foo)
  $c.foomethod    #-> foomethod
}

foo(Quux.new)