【问题标题】:Perl6: variable number of arguments to function/subroutinePerl6:函数/子例程的可变参数数量
【发布时间】:2019-07-09 18:44:12
【问题描述】:

我希望能够在 Perl6 中运行具有可变数量参数的函数,但是在阅读了https://docs.perl6.org/language/functions#Arguments 之后,我看不出它是如何实现的。我看到许多其他语言的链接,以及“标题相似的问题经常被否决”的警告,但我在文档或 StackOverflow 的任何地方都没有看到这一点。

我想做这样的事情:

some-test($v1, $v2)

some-test($v3)

但没有单独的函数,每个函数都使用multi

如何构造一个 Perl6 子例程来接受可变数量的字符串?

【问题讨论】:

  • 所以 $v3 与 $v1 的值不同?
  • @Scimon 给函数/子程序的所有变量都应该是独立的,所以是的,$v3 应该不同于 $v1

标签: raku


【解决方案1】:

TL;DR您问的是可变参数函数。1 简单的使用很简单。一些 P6 特性,尤其是位置参数和命名参数,以及可变参数解构,增加了一些皱纹。另外,请参阅其他也很有帮助的答案。

可变数量的参数

简单可变参数函数的简单使用:

sub variadic (|args) { say args .elems }
variadic();           # 0
variadic('1');        # 1
variadic('1', '2');   # 2

|foo 参数将所有剩余的参数汇总到绑定到无符号标识符 fooCapture 中:

sub variadic ($a, @b, %c, |others) { say others[0] }
variadic 1, [2,3], (:foo), 4, [5,6], (:bar); # 4

在上面的示例中,others 参数“slurps”14 开头的最后三个列出的参数。

可变变体

事情并不总是简单的:

variadic(1, 2);       # 2 -- works but args are Ints
variadic(:foo,:bar);  # 0 -- where did :foo, :bar go?
variadic((:foo));     # 1 -- works; arg is Pair (:foo)
variadic((1, 2));     # 1 -- works; arg is List (1,2)

在这个答案的其余部分我解释:

  • 约束参数,例如确保它们都是字符串。

  • 位置 vs 命名参数; **@foo*%foo

  • 可变位置解构; +@foo*@foo

约束参数

可变数量的字符串

您可以使用where 子句对含糊的参数施加任何您想要的约束。 (如果需要,您可以将其打包成subset。)

例如,将所有参数限制为Str 类型:

sub variadic (|args where .all ~~ Str) { say args .elems }
variadic();         # 0
variadic('1');      # 1
variadic('1', '2'); # 2
variadic(1);        # Constraint type check failed

位置 vs 命名参数; **@foo*%foo

P6 同时支持positional and named arguments

在签名中使用|foo 总是会捕获所有剩余的参数,包括位置参数和命名参数:

sub slurps-into-WHAT (|args) { say WHAT args }
slurps-into-WHAT(); # (Capture)

Capture 将位置参数存储在可通过位置下标访问的内部列表(即args[...])中,并将命名参数存储在可通过关联下标访问的哈希中(即args<...>args{...}):

sub variadic (|args) { say " {args[1]} {args<named2>}" }
variadic('pos0', 'pos1', named1 => 42, named2 => 99); # pos1 99

有时最好只收集命名参数,或者只收集位置参数,或者收集两者但在单独的参数中。

要收集命名参数,请使用*%foo 形式的参数(一个星号前缀和一个哈希参数):

sub variadic-for-nameds (*%nameds) { say %nameds }
variadic-for-nameds(:foo, :bar); # {bar => True, foo => True}

(请注意,所有方法都以这种方式收集命名参数,即使它们的签名没有明确说明。2

要收集位置参数,请使用**@foo 形式的参数(两个星号前缀紧跟一个数组参数):

sub variadic-for-positionals (**@positionals) { say @positionals }
variadic-for-positionals(1, 2, 3); # [1 2 3]

可变位置解构; +@foo*@foo

P6 提供了一系列非可变的argument destructuring features

第一个可变参数位置解构参数形式是+@foo。除了一种情况外,这与**@foo 具有完全相同的效果;如果可变参数只有一个参数,并且该参数是一个列表或数组,则该参数将绑定到该列表或数组的内容,剥离列表/数组容器:

sub variadic-plus (+@positionals) { say @positionals }
variadic-plus(1,2,3);   # says same as for **@positionals
variadic-plus(1);       # says same as for **@positionals
variadic-plus([1]);     # [1]     -- instead of [[1]]
variadic-plus((1,2,3)); # [1 2 3] -- instead of [(1 2 3)]

引入了+@foo 表单以支持"single arg rule"。它由编写内置插件的核心开发人员使用。用户可能希望在需要相同行为时使用它。

另一种可变参数位置解构形式是*@foo。它与+@foo 的作用相同,因为它从列表或数组容器 args 中提取内容并将容器丢弃。但它更具侵略性:

  • 它对所有参数都这样做。

  • 如果一个参数是一个列表而不是一个数组((...) 而不是[...]),那么它会下降到该列表中,如果列表中的一个元素本身就是另一个内部列表或数组,它会递归地重复该练习。

因此:

sub variadic-star (*@positionals) { say @positionals }
variadic-star((1,2),[3,4]);             # [1 2 3 4]
variadic-star((1,2),(3,4,(5,6,(7,8)))); # [1 2 3 4 5 6 7 8]
variadic-star((1,2),(3,4,[5,6,(7,8)])); # [1 2 3 4 5 6 (7 8)]

(请注意它是如何从 [5,6,(7,8)] 数组中剥离容器但没有下降到其中的。)

最后一件事;是否有可变参数命名解构参数?你告诉我。3

奖励部分:foo(...) vs foo (...)

(我加入了这个额外的部分,希望它能避免混乱。如果这部分本身令人困惑,请忽略它。)

例程调用可以在其参数列表周围加括号或不加括号,它们的含义相同。左括号必须紧跟在例程名称之后,中间没有空格:

sub foo  (|args)  { say args[0] }
foo 'a', 'b';    # a
foo('a', 'b');   # a

(此规则仅适用于例程 call,在例程名称及其 参数之间。不适用于例程 声明 ,在例程名称和它的参数之间。对于后者,声明,你可以不留空格或使用空格,就像我上面的sub foo (|args)一样,没有区别。)

如果您在调用中的 foo 和左括号之间插入空格,则您正在编写不同的内容:

foo  ('a', 'b'); # (a b)

使用 one 参数调用 foo,一个列表 ('a', 'b')foo('a', 'b') 使用 两个 参数调用 foo 形成对比,这两个值括号内。

以下调用foo 带有两个参数,这两个参数都是列表:

foo ('a', 'b', 'c'),  ('d', 'e', 'f'); # (a b c)

你可以嵌套括号:

foo(  ('a', 'b', 'c'),  ('d', 'e', 'f')  )    ; # (a b c)
foo  (('a', 'b', 'c'),  ('d', 'e', 'f'))      ; # ((a b c) (d e f))
foo( (('a', 'b', 'c'),  ('d', 'e', 'f')) )    ; # ((a b c) (d e f))

后两个foo 调用得到一个 参数,一个列表( ('a', 'b', 'c'), ('d', 'e', 'f') )(恰好包含两个内部列表)。

脚注

1 标准的行业术语是variadic function。在撰写此答案时,Rakudo P6 编译器在错误消息中使用行业标准术语(“variadic”),但官方 P6 文档倾向于使用“slurpy”一词而不是“variadic”,并谈到“slurping up arguments ”。

2 如果没有明确指定,方法总是有一个名为%_ 的隐式可变参数命名参数:

say .signature given my method foo {} # (Mu: *%_)

3 P6 语言和/或 Rakudo P6 编译器目前允许以 +%foo**%foo 的形式编写参数。对可变参数名称进行解构并没有什么意义。也许这解释了为什么这两种形式都会做一些疯狂的事情:

  • **%foo 似乎与 %foo 无法区分。

  • +%foo 似乎与 **@foo 没有区别,除了使用标识符 %foo 而不是 @foo。绑定到%foo 的对象是Array

【讨论】:

  • **+@ 的用途是什么?这些似乎没有指数和添加
  • 感谢您写下所有这些,我会尽快阅读。我非常感谢您为该答案所做的所有工作!
【解决方案2】:

如果您只想获取 1 或 2 个值,那么最简单的方法是将第二个值标记为可选:

sub some-test( $v1, $v2? ) { ... }

或者你可以定义一个默认值:

sub some-test( $v1, $v2="default" ) { ... }

或者,如果您想要任意数量的值(1 个或更多),您可以使用带有 where 子句的 slurpy:

sub some-test( *@v where *.elems > 0 ) { ... }

【讨论】:

  • 这失败了 Constraint type check failed in binding to parameter '@v'; expected anonymous constraint to be met but got Array ($["a", "b"]) :-(
  • 可以分享一下你使用的子程序定义吗? $["a","b"] 应该匹配 *.elems > 0 的 where 子句(因为它有 2 个元素)。
  • sub some-test (*@v where *.elems &lt; 0) { for @v -&gt; $v { put $v } } my $x = 'a'; my $y = 'b'; some-test($x, $y);
  • 所以你正在测试 elems 0 那么你不会接受一个空数组但会接受其他人
  • 获取至少一个值的另一种选择是使用sub some-test($v1, *@rest) { }
【解决方案3】:

您可以使用签名destructuring

sub some-test(*@all [$first, *@rest]) { ... } # must have 1 or more parameters

【讨论】:

    猜你喜欢
    • 2014-03-06
    • 2011-01-09
    • 1970-01-01
    • 2022-12-11
    • 1970-01-01
    • 2017-11-26
    • 2011-11-26
    • 2018-02-02
    • 1970-01-01
    相关资源
    最近更新 更多