为什么将 Perl 6 命名参数限制为确定值使其成为必需值?

Why does constraining a Perl 6 named parameter to a definite value make it a required value?

考虑这些都采用单个命名参数的子例程。命名参数应该是可选的,我还没有看到任何例外情况。

没有类型限制就没有问题;命名参数不是必需的。有了可以接受类型对象的类型约束(没有注解,: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 文档:

Normally, a type constraint only checks whether the value passed is of the correct type.

但是,我没有传递任何价值。我认为这些约束只对作业有影响。因为我没有明确提供要分配的值,所以我希望没有分配也没有问题。 Rakudo 2017.10 并非如此。这导致我以各种令人讨厌的方式解决这个问题。这与我的问题 有关,我试图区分零和一个参数的情况。

我可以通过分配默认值来解决这个问题,但在某些情况下,没有有意义的默认值。例如,Bool 很简单,但是什么 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();

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

希望对您有所帮助

所有参数总是有一些值,即使它们是可选的。我澄清了您在 379678 and 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类型的笑脸时,默认的default不再匹配类型约束。如果未选中它,您将失去指定 :D 约束的好处。例如,由于默认的默认类型对象会导致此例程的主体发生爆炸,因此期望参数是确定的值。

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

并回答指定 :D 是否会使参数自动成为必需的问题。我对这个想法持 -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();

回答我原来的问题:所有参数都是必需的。这只是他们如何获得价值的问题。它可以通过参数、显式默认值或隐式默认值(基于其类型约束)。

解决 brian 代码的问题

sub f ( :$f where { $^a.^name eq 'Any' or $^a ~~ Int:D } ) { ... }

这段代码丑陋、复杂,而且 stringly typed 没有充分的理由。

这是解决这些问题的代码:

sub f ( :$f where Any | Int:D ) { ... }

但是还是有问题。使用 Any 类型意味着 f :f(foo) 呼叫,其中 foo 恰好是 Any,将会通过。

相反,我们可以使用调用代码不可能使用的类型(假定以下内容在包中声明并从包外部使用):

my role NoArg {}
our sub f ( :$f where Int:D | NoArg = NoArg ) { ... }

这留下了最后一个问题:布赖恩方法中固有的 LTA 错误消息。

虽然以下代码目前会减慢代码速度,但它进一步提高了代码的可读性,如果传递了无效参数,自动生成的错误消息是完美的 cromulent (expected OptionalInt but got ...):

my role NoArg {}
my subset OptionalInt where Int:D | NoArg;
our sub f ( OptionalInt :$f = NoArg ) { ... }

这与命名参数无关

brian 的问题标题是:

Why does constraining a Raku named parameter to a definite value make it a required value?

布赖恩描述的情况与命名参数无关。这是由于 :D 约束。它适用于所有 参数、变量和标量容器:

sub quux ( Int:D :$quux ) {} # Named parameters require a definite value:
quux;                        # Error.

sub quux ( Int:D $quux? ) {} # Positional parameters do too:
quux;                        # Error

my Int:D $foo;               # And variables: Error.

my Int:D @array;             # And containers:
@array[42] := Nil;           # Error

Raku(do) 很有帮助

:D 类型“笑脸”表示 D 无限值。这就是它的目的。

如果用户代码无法为 any parameter/variable/container 提供 D 有限值,并且(仅) :D 约束,那么 Raku(do) 逻辑上 必须 做一些明智的事情来维护类型安全。这就是它正在做的。


考虑 Raku 的升级类型约束:

  1. 如果指定了no类型约束,则无需坚持任何特定类型的值。 Raku(do) 维护 memory safety,但仅此而已。

  2. 如果 parameter/variable/container 具有 Int(或 Int:_)类型约束,Raku(do) 确保分配或绑定到它的任何值都是 Int,但它不会坚持认为它是 definite Int.

  3. 如果 parameter/variable/container 有一个 :D 约束(例如 Int:D),那么 Raku(do) 坚持认为分配或绑定到它的任何值都是确定值,或者,如果不是,则通过抛出可恢复异常(如果安全)或终止程序(如果不安全)来维护类型安全。它做的一件事 而不是 做的是弥补一些神奇的默认值,因为正如布赖恩在他的问题中指出的那样:

    [Raku(do)] could work around this by assigning a default value, but ... what definite Int would fit? Whatever it is would be some magical value that would complicate and distract the code.


在下面的参数情况下,Raku(do) 拒绝将 quux 调用绑定到 quux sub 并通过抛出可恢复的异常来维护类型安全。如果开发人员愿意,他们可以选择 .resume,并且在他们的头上:

sub quux ( Int:D :$quux? ) { say 42 } # Does NOT display 42
quux;
CATCH { .resume }
say 99;                               # 99

在可变的情况下,Rakudo 可能再次能够维护内存安全:

my Int:D $quux = 42;
$quux = Nil;
CATCH { .say; .resume }  # Type check failed ...
say $quux;               # 42

也可能不会;在极端情况下,Raku(do) 失败并出现无法恢复的错误 compile-time:

my Int:D $quux; # "Error while compiling ... requires an initializer"
CATCH { .resume }
say 99;         # Does NOT display 99

在这种情况下,Raku(do) 必须 在编译期间退出以避免魔法值问题。

“所有参数都是必需的”是不正确的

布赖恩在他的回答中说“所有参数都是必需的”。

正如我上面所解释的,这不仅仅是关于参数,还有变量和标量容器。

说所有参数、所有变量和所有容器都是必需的有意义吗?那到底是什么意思?

brian 的心智模型与 Raku 的语法完全矛盾:

sub foo ($bar is required, $baz?) { ... }

是否需要$bar?是的。 $baz 是必需的吗? 没有.


想象一个包含两个字段的 Web 表单:带有 *required 注释的用户名字段,以及没有它或可能带有 *optional 注释的密码字段。此表单包含多少必填字段?大多数人会说它包含一个。这是 Raku 遵循的关于“必需”和“可选”这两个词的心智模型。

如果在表单后面编写代码的人指定密码字段必须包含一个确定的值,那么它不能以空白结束。也许代码会注入一个默认的“my_password”来满足定值约束。同样,这是 Raku 遵循的有关 :D 约束的心智模型。


是的,人们可能会对“可选参数”和“必需参数”感到困惑。布赖恩显然是。那么,也许我们在文档中至少使用过一次或两次“参数参数”?我不相信它会有所帮助。

此外,如果开发人员编写了“可选参数参数”,但还指定了 :D 约束,则此 SO 中描述的问题仍然适用。对调用参数“参数参数”的更改不会有帮助。

我认为 Rakoon 的共识是那些认为自己有更好的措辞、展示和讲述的人有责任。我对 brian 的“所有参数都是必需的”的回应是“每个人类问题总有 well-known 解决方案�� 简洁、合理且错误”。但亲爱的 reader,也许你有更好的主意?