Почему ограничение определенного параметра 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, потому что значение, которое я хочу, должно быть определено.

Из Signatures docs:

Обычно ограничение типа проверяет, имеет ли значение принятое значение.

Но я не передаю никакой ценности. Я полагал, что эти ограничения будут иметь значение только для задания. Поскольку я не предоставляю явное значение для назначения, я ожидал, что не будет никакого назначения и не будет проблем. Это не так с 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();

Ответ 1

Я решил это, проверив точно тип 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();

Чтобы ответить на мой исходный вопрос: требуются все параметры. Это просто вопрос о том, как они получают ценности. Это может быть аргумент, явное умолчание или неявное значение по умолчанию (основанное на его ограничении типа).

Ответ 2

Все параметры всегда имеют некоторое значение, даже если они являются необязательными. Я уточнил документы, которые вы ссылаетесь в 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, значение по умолчанию больше не соответствует типу ограничения. Если это не было отменено, вы потеряете преимущество указания ограничения :D. Поскольку объекты типа по умолчанию по умолчанию будут, например, вызывать взрыв в теле этой процедуры, который ожидает, что параметры будут определенными значениями.

sub (Int:D $a?, Num:D :$b) { say $a/$b }()

И чтобы ответить на титульный вопрос о том, нужно ли указывать параметр :D автоматически. Я нахожусь на этой идее, так как она вводит специальный случай, когда пользователям нужно будет просто запомнить один символ ввода (!, чтобы пометить параметр по мере необходимости). Это также приводит к менее полезной ошибке, которая говорит о arity или требуемых параметрах, а не о реальной проблеме: по умолчанию по умолчанию для параметров, не соответствующих типу typecheck в параметре.

Ответ 3

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" }

Надеюсь, что это поможет