В чем смысл принуждения типа 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 выполняет только безопасные преобразования/приведения типов.