像 Int(Cool) 这样的强制转换有什么意义?

What is the point of coercions like Int(Cool)?

The Perl 6 Web site on functions says

Coercion types can help you to have a specific type inside a routine, but accept wider input. When the routine is called, the argument is automatically converted to the narrower type.

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'

Here the Int is the target type to which the argument will be coerced, and Cool is the type that the routine accepts as input.

但是潜艇有什么意义呢? $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 的调用者只能提供一个可以转换为 BarFoofoo 甚至不能调用 [=18] 的方法=] 在 $c 上,因为它的类型 Bar。那么,为什么 foo 首先关心要强制转换的类型是 Foo 呢?

有人可以解释一下吗?链接到适当的文档和部分规范也很受欢迎。我在那里找不到任何有用的东西。

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

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

我认为两个类型不存在层次关系的示例没有意义。

它所做的是接受一个值,该值是 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);
}

所以这接受 IntStrRatFatRatNumArrayHash 等并尝试转换在使用它调用 &infix:<*> 之前将其转换为 Int,并且 2.

say double '  5  '; # 25
say double 2.5;     # 4
say double [0,0,0]; # 6
say double { a => 0, b => 0 }; # 4

您可以将其限制为 Cool 而不是 Any,因为所有 Cool 值都是本质上需要向 Int.

提供强制转换

( :( Int(Any) $ ) 可以缩短为 :( 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;
}

None跟用签名给你胁迫的一样清楚


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

my &double = * * 2; # WhateverCode
my &double = * × 2; # ditto

my &double = { $_ * 2 };       # bare block
my &double = { $^n * 2 };      # block with positional placeholder
my &double = -> $n { $n * 2 }; # pointy block

my &double = sub ( $n ) { $n * 2 } # anon sub
my &double = anon sub double ( $n ) { $n * 2 } # anon sub with name

my &double = &infix:<*>.assuming(*,2); # curried
my &double = &infix:<*>.assuming(2);

sub double ( $n ) { $n * 2 } # same as :( Any $n )

更新 今天看了这个答案后,我得出结论,我完全误解了@musiKk 的意思。 @darch's question 和@musiKk 的回复中最清楚地揭示了这一点:

@darch: Or is your question why one might prefer Int(Cool) over Int(Any)? If that's the case, that would be the question to ask.

@musiKk: That is exactly my question. :)

回顾我看到的许多其他答案 none 已经按照我现在认为值得解决的方式解决了这个问题。

我当然可能是错的,所以我决定做的是保留原来的问题,特别是保留原样的标题,保留原样的答案,而不是写一个新的答案地址@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 的签名中引入任何值的一揽子强制转换:

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

或:

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

现在,如果您在 double(prompt('')); 语句提示时键入 42,则 run-time type-check 失败不再适用,取而代之的是 run-time 尝试将字符串强制转换为 Int。如果用户键入 well-formed 数字,代码就可以正常工作。如果他们键入 123abc,强制将在 run-time 处失败,并显示一条很好的错误消息:

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

任何值的一揽子强制转换的一个问题是这样的代码:

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

在 run-time 处失败并显示消息:“未找到 class 'City' 的调用者的方法 'Int'”。

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

我们可以在任何值的无强制转换和一揽子强制转换之间选择一个平衡点。

强制转换的最佳 class 通常是 Cool class,因为 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' 未找到 [= 的调用者更好” 96=] 'City'".


why would foo care that the to-be-coerced type is a Foo in the first place?

希望现在很明显,值得将 coerce-from-type 限制为 Foo 的唯一原因是因为该类型有望成功强制转换为 Bar 值(或者,也许,失败并显示友好消息)。

Could someone shed some light on this? Links to appropriate documentation and parts of the spec are appreciated as well. I couldn't find anything useful there.

您最初引用的文档几乎就是最终用户文档的全部内容。希望它现在有意义并且你已经准备好了。如果没有请发表评论,我们将从那里开始。

我相信答案很简单,因为您可能不想将参数限制为 Int,即使您将在 sub 中将其视为 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

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

Cool 的所有子类型都将(按照 Cool 的要求)强制转换为 Int。因此,如果您的 sub 内部的运算符或例程仅适用于 Int 参数,则您不必添加额外的 statement/expression 转换为 Int 也不需要 operator/routine 的代码考虑 Cool 的其他子类型。它强制该参数将是一个 Int 在你的 sub 中,无论你在哪里使用它。

你的例子是倒退的:

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)

根据 ,@musiKk 问题的更好版本 "What is the point of coercions like Int(Cool)?" 结果是:

Why might one prefer Int(Cool) over Int(Any)?

一个推论是:

Why might one prefer Int(Any) over 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) 的例程而编写的绑定代码。如果静态分析显示没有其他候选例程能够接受调用,那么编译器将在编译时拒绝它。这是本答案最后一节中解释的 "strong typing" 的含义之一。

  • 如果值 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 年)时的定义,此类型检查和尝试的转换将是:

  • strong typing 在某种意义上,像 Int(...) 这样的类型约束是 "use of programming language types in order to both capture invariants of the code, and ensure its correctness, and definitely exclude certain classes of programming errors";

  • 目前在 Rakudo 中 弱类型 在某种意义上 Rakudo 在编译时不检查 Int(...) 中的 ... 即使理论上可以。也就是说,sub double (Int $x) {}; double Date; 会产生 编译时间 错误(Calling double(Date) will never work),而 sub double (Int(Cool) $x) {}; double Date; 会产生 运行 时间 错误 (Type check failed in binding).

  • type conversion;

  • weak typing in the sense that it's implicit type conversion 在编译器将处理 .Int 强制转换作为执行调用的一部分的意义上;

  • explicit type conversion 在某种意义上,Int(...) 约束明确指示编译器将转换作为绑定调用的一部分;

  • checked 显式类型转换——P6 只做类型安全 conversions/coercions.