В чем смысл принуждений, таких как 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)