Perl 6块中是一个参数还是没有参数?

6
什么是Perl 6中在没有明确签名的块中区分参数和无参数的方法?我没有任何实际用途,但我很好奇。
没有明确签名的块将值放入$_中:
my &block := { put "The argument was $_" };

签名实际上是 ;; $_? is raw。这是一个可选参数。在块中,@_变量未定义,因为没有明确的签名。
有无参数的情况,其中$_将未定义:
&block();  # no argument

但是,只有一个参数的情况下$_将是未定义的。类型对象始终是未定义的:

&block(Int);

然而,一个没有内容的$_实际上是一个Any(而不是Nil)。我无法区分这两种情况:

&block();
&block(Any);

以下是一个较长的例子:

my $block := {
    say "\t.perl is {$_.perl}";

    if $_ ~~ Nil {
        put "\tArgument is Nil"
        }
    elsif ! .defined and $_.^name eq 'Any' {
        put "\tArgument is an Any type object"
        }
    elsif $_ ~~ Any {
        put "\tArgument is {$_.^name} type object"
        }
    else {
        put "\tArgument is $_";
        }
    };

put "No argument: ";    $block();
put "Empty argument: "; $block(Empty);
put "Nil argument: ";   $block(Nil);
put "Any argument: ";   $block(Any);
put "Int argument: ";   $block(Int);

请注意,无参形式和任意参数形式显示相同的内容:
No argument:
    .perl is Any
    Argument is an Any type object
Empty argument:
    .perl is Empty
    Argument is Slip type object
Nil argument:
    .perl is Nil
    Argument is Nil
Any argument:
    .perl is Any
    Argument is an Any type object
Int argument:
    .perl is Int
    Argument is Int type object
3个回答

4
据我所知,没有显式签名的情况下,了解传递的参数数量的唯一方法是在函数体内使用@_,这将生成一个:(*@_)签名。
my &block := { say "Got @_.elems() parameter(s)" };
block;               # Got 0 parameter(s)
block 42;            # Got 1 parameter(s)
dd block.signature;  # :(*@_)

是的,如果您需要,老牌的@_仍然存在 :-)


1
如果您不想在 $_ 中设置值,那么它就存在了。而且 @_ 中的值是不可变的。 - brian d foy
如果你在一个没有显式签名的块中引用@_,那么“它在那里,如果你不想在$_中赋值的话。”(我认为Brian的意思是,如果你在一个没有显式签名的块中引用@_,那么该值不会在$_中。)然后就把它放在那里。例如{ $_ := .[0] if @_ }。是的,这很麻烦。但Liz的答案精确地回答了Brian在他的问题中所问的Perl 6的方式(“在没有显式签名的块中区分参数和无参数的方法是什么?”)。Brian指出,这意味着你必须手动绑定或分配$_,与他接受的答案中的巨大复杂性相比,这只是一个微小的问题。 - raiph
@_ 中的值是不可变的,只有它所绑定的对象是不可变的。同样的规则也适用于 $_。因此,{ $_++ }(42) 的结果是 caller postfix:<++> ... require[s] mutable argument。如果你想要可变性,只需在我之前的评论中将 := 替换为 = 即可。 - raiph

3
{ put $_.perl }

有点类似于这个:(但不起作用)
-> ;; $_? is raw = CALLERS::<$_> { put $_.perl }

由于块外部的 $_ 的默认值为 Any,如果在调用函数之前没有向 $_ 放入任何内容,则会得到 Any


要获取类似的东西并且可以区分,请使用Capture

my &foo = -> ;; |C ($_? is raw) {
    unless C.elems {
       # pretend it was defined like the first Block above
       CALLER::<$_> := CALLER::CALLERS::<$_>
    }
    my $called-with-arguments := C.elems.Bool;


    if $called-with-arguments {
        say 'called with arguments';
    } else {
        say 'not called with arguments';
    }
}

我知道这些。我问的是当我把“Any”放进去的情况。 - brian d foy
1
如果您需要此信息,请不要使用旨在使差异不可见的功能。 - Brad Gilbert
1
我并不需要它。我是在研究这种语言的工作方式和边界,以便告诉其他人他们不能做什么。 - brian d foy

1
这是我解决问题的方法。我很想以更简洁的方式实现,但语言的巧妙性使得我不得不绕过它。这适用于位置参数,但对于命名参数,有更深层次的诡计,我不会在这里处理。
我还有一个问题,为什么将Perl 6命名参数限制为明确的值会使其成为必需值?,答案澄清了实际上没有可选参数。只有具有默认值的参数,如果我没有显式分配,则有一个隐含的默认值。
我的问题的关键是我想知道何时给参数赋值,何时没有。我通过参数或显式默认值给它赋值。隐式默认值是正确类型的类型对象。如果我没有指定类型,则为Any。隐式默认值必须满足我指定的任何约束。
第一个目标是严格限制用户在调用代码时可以提供的值。如果未定义的值无效,则不应允许指定该值。
第二个目标是在代码中轻松区分特殊情况。我想减少某些深层代码部分需要了解的特殊知识量。
我可以通过显式分配一个特殊值来获取第三种情况(其中我知道没有参数或合适的默认值),我知道它不能是任何其他有意义的值。有一个比Any更无意义的值,那就是Mu。它是所有未定义值中最未定义的值。 AnyMu的两个子类型之一(另一个是Junction),但你在正常代码中几乎永远不会看到Mu出现在你的值中。用户代码中未定义的事物从Any开始。
我可以创建一个约束条件来检查我想要的类型或Mu,并设置默认值为Mu。如果我看到一个Mu,我知道没有参数,并且它是Mu,因为我的约束条件设置了这个。

由于我正在使用Mu,所以有些事情我做不到,比如使用===运算符。智能匹配无法工作,因为我不想测试继承链。我可以直接检查对象名称:

my $block := ->
    $i where { $^a.^name eq 'Mu' or $^a ~~ Int:D } = Mu
    {
    say "\t.perl is {$i.perl}";

    put do given $i {
        when .^name eq 'Mu'  { "\tThere was no argument" }
        when Nil             { "\tArgument is Nil"       }
        when (! .defined and .^name eq 'Any') {
            "\tArgument is an Any type object"
            }
        when .defined {
            "\tArgument is defined {.^name}"
            }
        default { "\tArgument is {.^name}" }
        }
    };

put "No argument: ";         $block();
put "Empty argument: ";      try $block(Empty); # fails
put "Nil argument: ";        try $block(Nil);   # fails
put "Any type argument: ";   try $block(Any);   # fails
put "Int type argument: ";   try $block(Int);   # fails
put "Int type argument: ";   $block(5);

现在大多数调用失败是因为它们没有指定正确的内容。 如果这些是例程,我可以为少量情况制作多路复用器,但最终这甚至是更糟糕的解决方案。如果我有两个参数,我需要四个多路复用器。如果有三个这样的参数,我需要六个。那将产生大量的样板代码。但是,块不是例程,所以这里无关紧要。

这是我解决这个问题的方法。这个答案使用了签名。问题是“在没有显式签名的块中,Perl 6的区分参数和无参数的方式是什么?”这个答案也非常复杂。Liz的答案简单、正确,并适用于没有显式签名的块。 - raiph
Liz的答案没有限制参数的数量。我想在没有签名的情况下完成它,但是找不到方法(这通常是问题和最终答案的情况)。 - brian d foy
遵循Liz的建议,编写像{ fail unless +@_ == 1 }或其他你想要的方案(例如given +@_ { when 0 { ... }; when 1 { ... }; default { fail } };)这样的保护机制有什么问题呢?如果出于某种原因仍然不够好并且必须使用签名,为什么不遵循Brad的建议,仅使用-> |args where { +args == 1 or fail 'nope' } ($arg) { }或类似方式? - raiph
我的回答已经充分回应了你的评论。继续进行吧。 - brian d foy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接