В чем смысл принуждений, таких как Int(Cool)?

веб-сайт Perl 6 по функциям говорит

типы принуждения могут помочь вам иметь определенный тип внутри подпрограммы,но принимать более широкий ввод. При вызове подпрограммы аргумент автоматически преобразуется в более узкий тип.

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)

здесь invocant из foo ограничено для предоставления Foo который может быть преобразован в Bar но foo не может даже вызвать метод Foo on $c типа is Bar. Так почему бы foo позаботьтесь о том, чтобы принудительный тип был Foo в первую очередь?

может кто-нибудь пролить свет на это? Ссылки на соответствующую документацию и части спецификации также приветствуются. Я не нашел там ничего полезного.

5 ответов


указать тип параметра, без принуждения: 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);

сбой во время выполнения с сообщением: "метод 'Int' не найден для вызывающего класса 'City'".

укажите тип параметра, с принуждением от прохладных значений:Int(Cool) $x

мы можем выбрать точку равновесия между отсутствием принуждения и общим принуждением любой ценности.

лучший класс для принуждения часто является 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 потому что это тип, который, как ожидается, успешно принудит к Bar значение (или, возможно, не с дружественным сообщением).

может кто-нибудь пролить свет на это? Ссылки на соответствующую документацию и части спецификации также ценятся. Я не нашел там ничего полезного.

документ, который вы первоначально процитировали, - это почти все, что есть для enduser doc. Надеюсь, теперь это имеет смысл, и вы все готовы. Если нет, пожалуйста, прокомментируйте, и мы пойдем оттуда.


что это делает, это принять значение, которое является подтипом прохладный, и пытается преобразовать его в Int. В этот момент is an Int независимо от того, что это было раньше.

так

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

действительно можно подумать о том, как (я думаю, что это было фактически реализовано в Ракудо )

# 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, крыса, FatRat, Num, массив, хэш, etc. и пытается преобразовать его в Int перед вызовом &infix:<*>, и 2.

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

вы можете ограничить его в прохладный вместо любой, как все прохладный значения по существу необходимы для обеспечения принуждения к 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;
}

ни один из них не так ясно, как тот, который использует подпись, чтобы принудить его для вас.


как правило, для такой простой функции вы можете использовать один из них и он, вероятно, сделает то, что вы хотите.

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 )

Я что-то пропустила? Я не эксперт Perl 6, но, похоже, синтаксис позволяет независимо указывать оба какие типы входных данных допустимы и как входные данные будут представлены функции.

ограничение допустимого ввода полезно, потому что это означает, что код приведет к ошибке, а не к бесшумному (бесполезному) преобразованию типа, когда функция вызывается с бессмысленным параметром.

Я не думаю, что пример, когда два типа не находятся в иерархических отношениях, имеет смысл.


Я считаю, что ответ так же прост, как вы не хотите ограничивать аргумент 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

все подтипы Cool будет (как круто требует от них) принужден к Int. Поэтому, если оператор или подпрограмма, внутренняя для вашего суб, работает только с 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)