В чем смысл принуждения типа 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
просто a 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
на $c
, потому что его тип Bar
. Итак, почему бы foo
ухаживать за тем, чтобы тип, который был бы в принудительном состоянии, был foo
в первую очередь?
Может ли кто-нибудь пролить свет на это? Также приветствуются ссылки на соответствующую документацию и части спецификации. Я не мог найти там ничего полезного.
Ответы
Ответ 1
Обновление Изучив этот ответ сегодня, я пришел к выводу, что совершенно не понял, к чему стремился @musiKk. Это было выявлено наиболее четко в вопросе @darch и ответе @musiKk:
@darch: Или ваш вопрос, почему кто-то может предпочесть Int (Cool), а не 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")
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'
Одной из проблем с принудительным приведением значения Any является такой код:
class City { ... } # City has no Int coercion
my City $city;
double($city);
не выполняется во время выполнения с сообщением: "Метод 'Int' не найден для инвоканта класса 'City'".
Укажите тип параметра с приведением значений 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; ожидается Cool, но получил City (City)", что, возможно, немного лучше диагностически для программиста, чем "Метод Int", не найденный для инвоканта класса "City".
почему бы foo заботиться о том, чтобы тип, который будет приведен к принуждению, был Foo?
Надеемся, что теперь очевидно, что единственная причина, по которой стоит ограничить coerce-from-type Foo
заключается в том, что тип, который должен успешно привести к значению Bar
(или, возможно, потерпеть неудачу с дружественным сообщением).
Может ли кто-то пролить свет на это? Ссылки на соответствующую документацию и части спецификации также приветствуются. Я не мог найти ничего полезного там.
Документ, который вы первоначально процитировали, - это почти все, что есть для enduser doc. Надеюсь, теперь это имеет смысл, и все готово. Если нет, пожалуйста, прокомментируйте, и мы пойдем оттуда.
Ответ 2
Что это значит, это принять значение, которое является подтипом Cool, и пытается преобразовать его в Int. В этот момент он есть 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, Rat, FatRat, Num, Array, Hash и т.д. и пытается преобразовать его в 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
Вы можете ограничить его классом Cool вместо Any, поскольку все значения Cool необходимы, чтобы обеспечить принуждение к Int.
(:( Int(Any) $ )
можно сократить до :( Int() $ )
)
Причина, по которой вы можете сделать это, - это то, что вам нужно быть Int
внутри sub, потому что вы вызываете другой код, который выполняет разные вещи с разными типами.
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 )
Ответ 3
Я что-то упустил? Я не эксперт Perl 6, но кажется, что синтаксис позволяет отдельно указать как допустимые типы ввода, так и , как будет представлен вход функции.
Ограничение допустимого ввода полезно, потому что это означает, что код приведет к ошибке, а не тихую (бесполезную) преобразование типа при вызове функции с бессмысленным параметром.
Я не думаю, что пример, когда два типа не находятся в иерархическом отношении, имеет смысл.
Ответ 4
Я считаю, что ответ прост, так как вы не можете ограничивать аргумент Int
, хотя вы будете рассматривать его как Int
в пределах sub. например, по какой-то причине вы хотите иметь возможность умножать массив на хэш, но терпеть неудачу, если аргументы не могут рассматриваться как 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 a multi
Ответ 5
Все подтипы Cool
будут (как это требует 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)
Ответ 6
По комментариям на оригинальный вопрос, лучшая версия вопроса @musiKk "Какой смысл принуждений, таких как Int (Cool)?" Оказалось, что:
Почему можно предпочесть Int(Cool)
Int(Any)
?
Следствие, которое я также рассмотрю в этом ответе:
Почему можно 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
метод преобразования .Int
. Таким образом, он не выдаст ошибку Method not found
во время выполнения, и на него можно положиться, чтобы предоставить хорошее сообщение об ошибке, если он не может преобразовать в целочисленное значение.
Почему можно Int(Any)
предпочтение Int(Any)
Int(Cool)
?
Поскольку Int(Any)
немного слабее, введя аргумент, он может быть любого обычного типа, а P6 просто попытается заставить его работать:
-
.Int
будет вызываться для аргумента, который передается подпрограмме, соответствующий параметр которой имеет ограничение типа Int(...)
независимо от того, что ...
является. При условии, что переданный аргумент имеет метод .Int
вызов и последующее преобразование имеют шанс на успех.
-
Если .Int
неудачно, то сообщение об ошибке будет таким, каким .Int
метод .Int
. Если аргумент на самом деле Cool
тогда метод .Int
выдаст хорошее сообщение об ошибке, если он не преобразуется в Int
. В противном случае метод .Int
предположительно не является встроенным, и результатом будет удача банка.
Почему Foo(Bar)
на первом месте?
И что это за слабая и сильная типизация?
Ограничение Int(...)
на параметр функции приведет к либо:
-
Неспособность проверить тип; или же
-
Преобразование .Int
соответствующего аргумента, которое .Int
к его целочисленному значению - или не удается, оставляя соответствующий параметр, содержащий Failure
.
Используя определения Википедии, какими они были на момент написания этого ответа (2019), эта проверка типов и попытка преобразования будут:
-
строгая типизация в том смысле, что ограничение типа типа Int(...)
- это "использование типов языка программирования для того, чтобы как захватить инварианты кода, так и обеспечить его корректность и определенно исключить определенные классы ошибок программирования";
-
В настоящее время слабая типизация в Rakudo в том смысле, что Rakudo не проверяет ...
в Int(...)
во время компиляции, хотя теоретически это возможно. То есть sub double (Int $x) {}; double Date;
sub double (Int $x) {}; double Date;
выдает ошибку времени компиляции (Calling double(Date) will never work
), тогда как sub double (Int(Cool) $x) {}; double Date;
sub double (Int(Cool) $x) {}; double Date;
выдает ошибку времени выполнения (Type check failed in binding
).
-
преобразование типов;
-
слабая типизация в том смысле, что она подразумевает преобразование типов в том смысле, что компилятор будет обрабатывать приведение .Int
как часть выполнения вызова;
-
явное преобразование типов в том смысле, что ограничение Int(...)
явно указывает компилятору выполнять преобразование как часть привязки вызова;
-
проверил явное преобразование типов - P6 выполняет только безопасные преобразования/приведения типов.