Почему ограничение именованного параметра Perl 6 определенным значением делает его обязательным?
Рассмотрим эти подпрограммы, которые все принимают один именованный параметр. Именованные параметры должны быть необязательными, и я не видел ничего, что могло бы сказать, что есть исключения из этого.
Без ограничений типа нет проблем; указанный параметр не обязателен. С ограничением типа, которое может принимать объект типа (без аннотации, :U
, а также :_
) нет проблем.
Parameter '$quux' of routine 'quux' must be an object instance of type 'Int',
not a type object of type 'Int'. Did you forget a '.new'?
in sub quux at /Users/brian/Desktop/type.p6 line 16
in block <unit> at /Users/brian/Desktop/type.p6 line 37
С ограничением типа, которое требует определенного значения (аннотировано :D
) названный параметр больше не является обязательным. То есть с любым другим определением мне не нужно указывать значение. С :D
Я должен предоставить значение. Я бы предпочел не пропускать :D
потому что значение, которое я хочу, должно быть определено.
Из документов подписей:
Обычно ограничение типа только проверяет, имеет ли переданное значение правильный тип.
Но я не передаю никакой ценности. Я полагал, что эти ограничения будут иметь значение только для назначения. Так как я не предоставляю явно значение для назначения, я ожидал, что назначения не будет и нет проблем. Это не относится к Rakudo 2017.10. Это заставляет меня обойти это различными неприятными способами. Это связано с моим вопросом. Это один аргумент или нет для блока Perl 6? где я пытаюсь различить случаи с нулевым и одним аргументом.
Я мог бы обойти это, назначив значение по умолчанию, но в некоторых случаях нет значений по умолчанию, которые имеют смысл. Bool
это легко, например, но что однозначно Int
подойдет? Что бы это ни было, это будет магическая ценность, которая усложнит и отвлечет код. Я сделал это с Perl 6 имеет Infinite Int, но мне это сходит с рук, потому что Inf
работает в качестве допустимого значения в этом случае.
sub foo ( :$foo ) {
put $foo.defined ?? 'foo defined' !! 'foo not defined';
}
sub bar ( Int :$bar ) {
put $bar.defined ?? 'bar defined' !! 'bar not defined';
}
sub baz ( Int:U :$baz ) {
put $baz.defined ?? 'baz defined' !! 'baz not defined';
}
sub quux ( Int:D :$quux ) {
put $quux.defined ?? 'quux defined' !! 'quux not defined';
}
sub quack ( Int:_ :$quack ) {
put $quack.defined ?? 'quack defined' !! 'quack not defined';
}
foo();
foo( foo => 2 );
bar();
bar( bar => 2 );
baz();
baz( baz => Int );
quack();
quack( quack => 2 );
quux( quux => 2 );
quux();
3 ответа
Я решил это, проверив точно тип Any
или умное соответствие для того, что я на самом деле хочу:
sub f ( :$f where { $^a.^name eq 'Any' or $^a ~~ Int:D } ) {
put $f.defined ?? "f defined ($f)" !! 'f not defined';
}
f( f => 5 );
f();
Чтобы ответить на мой оригинальный вопрос: все параметры обязательны. Это просто вопрос того, как они получают ценности. Это может быть аргумент, явное значение по умолчанию или неявное значение по умолчанию (на основе ограничения типа).
Все параметры всегда имеют какое-то значение, даже если они являются необязательными. Я уточнил документы, на которые вы ссылаетесь в 379678 и b794a7.
Необязательные параметры имеют значения по умолчанию по умолчанию, которые являются типом объекта явного или неявного ограничения типа (неявное ограничение Any
для рутины и Mu
для блоков).
sub (Int $a?, Num :$b) { say "\$a is ", $a; say "\$b is ", $b }()
# OUTPUT:
# $a is (Int)
# $b is (Num)
Выше значения по умолчанию по умолчанию передают ограничение типа для параметров. И то же самое в случае, если вы используете :U
или же :_
типа смайлики.
Тем не менее, когда вы используете :D
тип smiley, значение по умолчанию больше не соответствует ограничению типа. Если это не будет отмечено, вы потеряете преимущество указания :D
ограничение. Так как объекты типа по умолчанию по умолчанию, например, вызовут взрыв в теле этой подпрограммы, который ожидает, что параметры будут определенными значениями.
sub (Int:D $a?, Num:D :$b) { say $a/$b }()
И ответить на главный вопрос о том, является ли указание :D
должен сделать параметры, необходимые автоматически. У меня -1 по идее, так как он вводит особый случай, пользователям придется учиться только для того, чтобы сохранить один символ ввода (!
отметить параметр как требуется). Это также приводит к менее полезной ошибке, которая говорит о арности или обязательных параметрах, а не о фактической проблеме: значение по умолчанию для параметров по умолчанию не проходит проверку типов для параметра.
FWIW, похожая проблема существует с необязательными позиционными параметрами:
sub a(Int:D $number?) { ... }
a; # Parameter '$number' of routine 'a' must be an object instance of type 'Int', not a type object of type 'Int'. Did you forget a '.new'
Та же проблема возникает, если указать тип объекта по умолчанию:
sub a(Int:D $number = Int) { ... };
a; # # Parameter '$number' of routine 'a' must be an object instance of type 'Int', not a type object of type 'Int'. Did you forget a '.new'
Боюсь, это всего лишь следствие :D
ограничение применительно к параметрам: вы просто должны указать определенное значение по умолчанию, чтобы его можно было вызывать без каких-либо параметров.
Конечно, другим подходом может быть использование multi sub
и обязательный именованный параметр:
multi sub a() { say "no foo named" }
multi sub a(Int:D :$foo!) { say "foo is an Int:D" }
Надеюсь это поможет
Этот ответ:
Объясняет, что эта ситуация применяется ко всем параметрам и переменным.
Объясняет, почему ограничение определенного значения заставляет Раку / Ракудо жаловаться.
Предоставляет надежное решение для объединения необязательного параметра с ограничением определенного значения.
Ситуация касается всех :D
ограниченные параметры и переменные
Почему ограничение именованного параметра Raku определенным значением делает его обязательным?
Это не просто именованные параметры. Ваш пример:
sub quux ( Int:D :$quux ) {}
quux # "Parameter '$quux' ... must be an object instance ..."
Но то же самое верно и для необязательного позиционного:
sub quux ( Int:D $quux? ) {}
quux # "Parameter '$quux' ... must be an object instance ..."
Раку требует ценности для :D
тоже переменная:
my Int $foo; # Won't complain if you constrain to a plain Int
my Int:U $foo; # Int:U is fine too
my Int:_ $foo; # Still fine
my Int:D $foo; # "... type Int:D requires an initializer"
Почему ограничение определенным значением заставляет Раку / Ракудо жаловаться
Во-первых, помимо NativeCall, Raku/Rakudo безопасен для памяти. Таким образом, хотя пользователи могут притворяться или думать, что переменной или параметру не присвоено / не привязано значение, на самом деле оно всегда должно быть, если пользовательский код может получить к нему доступ. Таким образом, каждая переменная / параметр должна иметь неявное начальное значение по умолчанию, которым оно будет инициализировано, если оно не имеет явного значения.
Раку незаметно заботится о сохранении памяти, если вы не просите его творить магию. Если вы вообще не укажете тип, начальным значением по умолчанию будетMu
тип объекта. Если вы укажете такой тип, какInt
, Int:U
, или Int:_
, это Int
тип объекта. Но если вы укажетеInt:D
без указания начального значения по умолчанию он отказывается, потому что видит все так же, как и вы:
Я мог бы обойти это, назначив значение по умолчанию, но... какой определенный
Int
подойдет? Что бы это ни было, это будет некая магическая ценность, которая усложнит и отвлечет код.
Раку считает эту проблему неразрешимой без вашей помощи. Он считает, что выбор некоторого произвольно определенного значения в общем случае, независимо от того, какая эвристика была выбрана, приведет к множеству ошибок. Так что он отказывается спокойно заниматься проблемой.
В случае с параметрами он вызывает возобновляемое исключение:
sub quux ( Int:D :$quux? ) { say 42 } # Does NOT display 42
quux;
CATCH { .resume }
say 99; # 99
Ракудо отказывается связывать quux
позвонить в quux
sub и вместо этого выдает исключение. Этого достаточно, чтобы сохранить память. Если вы решите продолжить, то это будет на вашей голове.
В случае с переменными Rakudo не может поддерживать безопасность памяти (учитывая, что он отказывается выбрать произвольное определенное значение) и немедленно выходит из программы, не генерируя исключения:
my Int:D $quux; # "Error while compiling ... requires an initializer"
CATCH { .resume }
say 99; # Does NOT display 99
Есть скрытая двусмысленность словарного запаса, которая может способствовать чувству удивления по поводу такого поведения. Параметр можно назвать "необязательным". Это означает, что аргумент, соответствующий этому параметру, является необязательным, а не то, что сам параметр или значение, присвоенное / привязанное к этому параметру, является необязательным.
Надежное решение для объединения необязательного параметра с определенным ограничением значения
Вы можете:
Установите значение по умолчанию, которое пользователь не будет передавать;
Примените ограничение, которое является либо этим значением, либо каким-либо другим ограничением, которое вы хотите применить.
Решение Брайана было шагом к этому. Вот более чистая версия того же решения:
subset OptionalInt where Any:U | Int:D;
sub f ( OptionalInt :$f ) { say $f }
f( f => 5 ); # 5
f(); # (Any)
f( f => Any); # (Any)
f( f => 'a' ); # expected OptionalInt but got Str ("a")
Несмотря на улучшение (более легкий для чтения код, более быстрое и приятное сообщение об ошибке), вышеуказанное решение по-прежнему имеет слабость, заключающуюся в том, что код, который случайно вызвал sub f
с Any
аргумент будет вести себя так, как если бы он не передал аргумент.
Чтобы устранить эту слабость:
class NoArgPassed {}
subset OptionalInt where NoArgPassed | Int:D;
sub f ( OptionalInt :$f = NoArgPassed ) { say $f }
f( f => 5 ); # 5
f(); # (NoArgPassed)
f( f => Any); # expected OptionalInt but got Any (Any)
f( f => 'a' ); # expected OptionalInt but got Str ("a")