Perl6:函数/子例程的可变参数个数

con*_*con 11 perl6

我希望能够在Perl6中运行具有可变数量参数的函数,但在通过https://docs.perl6.org/language/functions#Arguments阅读后,我看不出它是如何完成的.我看到其他语言的链接很多,并且警告"类似标题的问题经常被低估"但我在文档或StackOverflow中没有看到这一点.

我想做这样的事情:

some-test($v1, $v2)
Run Code Online (Sandbox Code Playgroud)

要么

some-test($v3)
Run Code Online (Sandbox Code Playgroud)

但没有multi为每个使用单独的功能

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

rai*_*iph 9

TL; DR你问的是可变函数.1简单使用很简单.一些P6特性,最值得注意的是位置和命名参数,以及可变参数解构,增加了一些皱纹.另外,请参阅其他非常有用的答案.

可变数量的参数

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

sub variadic (|args) { say args .elems }
variadic();           # 0
variadic('1');        # 1
variadic('1', '2');   # 2
Run Code Online (Sandbox Code Playgroud)

一个|foo参数将所有剩余的参数填充到一个Capture绑定到无法识别的标识符中foo:

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

在上面的例子中,others参数"slurps" 1以最后三个列出的参数开头4.

变异的变化

事情并不总是那么简单:

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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 变元定位解构; +@foo*@foo

约束论点

可变数量的字符串

您可以使用where子句对slurped参数强加任何约束.(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
Run Code Online (Sandbox Code Playgroud)

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

P6支持位置命名参数.

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

sub slurps-into-WHAT (|args) { say WHAT args }
slurps-into-WHAT(); # (Capture)
Run Code Online (Sandbox Code Playgroud)

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

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

有时候最好只收集命名的args,或者只收集位置的args,或者收集两者但是在不同的参数中.

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

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

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

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

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

变元定位解构; +@foo*@foo

P6提供了一系列非可变参数解构功能.

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

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)]
Run Code Online (Sandbox Code Playgroud)

+@foo引入该表格是为了支持"单一规则".核心开发人员使用它编写内置的ins.用户可能希望在他们想要相同的行为时使用它.

其他可变位置解构形式是*@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)]
Run Code Online (Sandbox Code Playgroud)

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

最后一件事; 有可变的命名解构参数吗?你告诉我.3

奖金部分:foo(...)vsfoo (...)

(我包括了这个奖金部分,希望它能避免混淆.如果这部分本身令人困惑,那就忽略它.)

常规调用可以在其参数列表周围写入或不加括号,它们的含义相同.左括号必须立即遵循例程名称,而不插入空格:

sub foo  (|args)  { say args[0] }
foo 'a', 'b';    # a
foo('a', 'b');   # a
Run Code Online (Sandbox Code Playgroud)

(此规则仅适用于日常通话,程序名称及其之间的争论,它并不适用于常规申报,程序名称和它之间的参数.对于后者,声明,你可以不留下任何空间或利用空间正如我上面提到的那样sub foo (|args)并没有什么区别.)

如果你foo在一个调用中的左括号和左括号之间插入空格,你会写一些不同的东西:

foo  ('a', 'b'); # (a b)
Run Code Online (Sandbox Code Playgroud)

foo一个参数调用,一个列表与两个参数的调用('a', 'b')形成对比,括号内的两个值.foo('a', 'b')foo

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

foo ('a', 'b', 'c'),  ('d', 'e', 'f'); # (a b c)
Run Code Online (Sandbox Code Playgroud)

您可以嵌套括号:

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))
Run Code Online (Sandbox Code Playgroud)

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

脚注

1标准的行业术语是可变函数.在撰写本答案时,Rakudo P6编译器在错误消息中使用了行业标准术语("可变参数"),但官方P6文档倾向于使用"slurpy"而不是"variadic"这个词,并谈论"诋毁论据" ".

2%_如果未明确指定方法,则方法始终具有隐式可变参数命名参数:

say .signature given my method foo {} # (Mu: *%_)
Run Code Online (Sandbox Code Playgroud)

3的P6语言和/或Rakudo P6编译当前允许的形式被写入参数+%foo**%foo.拥有可变参数名称解​​构并没有多大意义.也许这就解释了为什么这两种形式都做了疯狂的事情:

  • **%foo似乎无法区分%foo.

  • +%foo似乎与**@foo使用标识符%foo而不是使用标识符无法区分@foo.绑定的对象%foo是一个Array!

  • 感谢您编写所有内容,我将尽快阅读。我非常感谢您为该答案所做的所有工作! (2认同)

Sci*_*mon 7

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

sub some-test( $v1, $v2? ) { ... }
Run Code Online (Sandbox Code Playgroud)

或者您可以定义默认值:

sub some-test( $v1, $v2="default" ) { ... }
Run Code Online (Sandbox Code Playgroud)

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

sub some-test( *@v where *.elems > 0 ) { ... }
Run Code Online (Sandbox Code Playgroud)


uge*_*exe 7

您可以使用签名解构

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