关于语法:这两个函数调用约定有什么区别?

What's the difference these two function calling conventions?

函数可以通过以下几种方式调用:

1
2
say(1, 2, 3) # 123
say: 1, 2, 3 # (1, 2, 3)

后者似乎传递了一个Positional,但除此之外我不知道它们还有什么不同。有什么重要的区别吗?你会使用哪种情况而不是另一种情况?


@jjmerelo 的回答涵盖了基础知识。这个补充性答案旨在有点详尽但希望不会令人筋疲力尽,涵盖了陷阱、罕见情况和建议。

foo: valuea, valueb, ...

也许令人惊讶的是,这不是对名为 foo 的子或方法的调用。

它是一个以标签开头的语句,foo:.

您问题中的 say: 行在普通程序中不起作用:

1
say: ; # Useless use of constant value a b c ...

"无用使用"警告意味着 没有以有用的方式使用。 say: 没有对值列表做任何事情。它只是一个不做任何事情的标签。

大概您正在使用类似 Perl 6 REPL 的东西。如果没有使用,REPL 会自动 says 行中的最后一个值,从而使该行看起来可以在没有警告的情况下工作。

.a-method:

如果使用 .a-method 形式的后缀方法调用除了调用者(. 左侧的参数,如果没有显式调用者,则为当前主题)之外没有参数,那么你可以只需将其写成:

1
42.say ;

您可以选择附加一个冒号:

1
42.say: ;

没有很好的理由,但它符合:

.a-method: arg2, arg3, ...

如果你想为后缀 .a-method 调用提供一个或多个参数(除了调用者),那么你必须选择两种方法中的一种来引入它们。

一种方法是在方法名称之后、参数之前立即写一个冒号。方法名和冒号之间不能有空格,冒号后面必须有空格,方法参数前面必须有空格。1

例如,以下方法调用中的 Numeric 参数前使用冒号:

1
say  .first: Numeric ; # 2

在上述行中,方法调用表达式 (.first: Numeric) 在语句终止符 (;) 处结束。如果有一个封闭的子表达式,例如数组下标,则方法调用表达式在该子表达式的末尾结束:

1
say .[1 + .first: Numeric] given  ; # ghi

冒号形式的方法调用的参数列表也被一个有效的语句修饰符关闭,比如 given:

1
say .first: Numeric given  ; # 2

a-sub arg1, arg2, ...

这是子程序调用的对应形式。唯一的格式差异是子名称前没有调用者或 . 并且您必须省略子名称后的冒号。

.a-method( arg2, arg3, ... )
a-sub( arg1, arg2, ... )

用于方法和子调用的另一种常见形式是在方法或子名称之后立即使用括号来分隔参数。开头的括号必须紧跟在后面,例程名称和 (.

之间没有任何空格

这里与 .first 方法一起使用的括号:

1
say 1 + .first(Numeric) given  ; # 3

这有一个优点,可以说它比使用外部括号的替代方法更漂亮:

1
say 1 + (.first: Numeric) given  ; # 3

如果您想直接在双引号字符串中放置子调用,您需要在子名称前面加上 & 符号并使用后缀括号形式:

1
2
my @array =  ;
say"first number is &first(Numeric,@array)" ; # first number is 2

要进行方法调用,您必须再次使用后缀括号形式,并且还必须提供显式调用者(您不能只写 "Some text .a-method()"):

1
2
my @array =  ;
say"first number is @array.first(Numeric)" ; # first number is 2

如果没有参数(除了方法调用的调用者),如果你想在字符串中插入子或方法调用,你仍然需要使用这个带有空括号的表单:

1
2
3
4
5
my @array =  ;
say"no method call @array[3].uc" ;     # no method call ghi.uc
say"with method call @array[3].uc()" ; # with method call GHI
say"&rand";                            # &rand
say"&rand()";                          # 0.929123203371282

.a-method ( arrgh, arrgh, ... ) ;

这行不通。

因为 .a-method 后面没有冒号,所以方法调用被认为是完整的。

这意味着接下来的事情必须是像 ; 这样的表达式/语句结束符,或者是对方法调用的结果进行操作的后缀运算符,或者是对结果和一些后续参数进行操作的中缀运算符.

( arrgh, arrgh, ... ) 不是这些。所以你会得到一个"连续两个术语"编译错误。

.a-method:( arrgh, arrgh, ... ) ;
.a-method: ( arrgh, arrgh, ... ) ;

一般来说,不要将 : 的使用与在参数周围使用括号作为方法调用的一部分混合使用。这样做没有充分的理由,因为它要么不起作用,要么只是偶然起作用,要么起作用但很可能使读者感到困惑。

如果冒号和左括号之间没有空格,会产生一个神秘的编译错误:

1
This type (QAST::WVal) does not support positional operations

留出空间似乎可行——但通常只能靠运气:

1
say .first: (Numeric) given  ; # 2

(Numeric) 是括号中的单个值,它产生 Numeric 所以这一行与:

相同

1
say .first: Numeric given  ; # 2

但是如果括号中有两个或多个参数,事情就会出错。使用以下形式之一:

1
2
say .first: Numeric, :k given  ; # 1
say .first(Numeric, :k) given  ; # 1

正确生成 2 元素的数组索引("key"),而不是:

1
say .first: (Numeric, :k) given  ; # Nil

产生 Nil 因为 .first 方法对单个参数没有任何用处,该参数是 (Numeric, :k).

形式的列表

当然,您可能偶尔需要传递一个参数,它是括号中的值列表。但是您可以在不使用冒号的情况下这样做。为了清楚起见,我建议您改为将其写为:

1
invocant.a-method(( valuea, valueb, ... ));

a-sub ( arrgh1, arrgh2, ... ) ;

正如刚刚解释的方法调用,这将一个参数传递给a-sub,即单个列表( arrgh1, arrgh2, ... ),这很少是作者的意思。

同样,我的建议是改为:

1
`a-sub( valuea, valueb, ... ) ;`

或:

1
`a-sub  valuea, valueb, ...   ;`

如果您要传递多个参数,或者如果您希望将列表作为单个参数传递,则:

1
`a-sub(( valuea, valueb, ... )) ;`

.a-method : arrgha, arrghb, ...
a-sub : arrgha, arrghb, ...

对于方法形式,这会给您带来"困惑"的编译错误。

如果 a-sub 不带参数,则子表单也是如此。如果 a-sub 接受参数,您将得到一个"前面的上下文需要一个术语,但发现中缀:而不是"编译错误。

.&a-sub

有一种调用形式,可以让您调用声明为子的例程——但使用 .method 调用语法。下面将点左侧的 "invocant" qux 作为第一个参数提供给名为 a-sub 的 sub:

1
qux.&a-sub

像往常一样使用 : 或括号将附加参数传递给 a-sub:

1
2
3
sub a-sub ($a, $b) { $a == $b }
say 42.&a-sub(42), 42.&a-sub(43); # TrueFalse
say 42.&a-sub: 42;                # True

(在本节的原始版本中,我写道不能传递额外的参数。我已经对此进行了测试,并认为不能。但我一定是被某些东西弄糊涂了。@Enheh 的评论使我重新测试并发现可以像普通方法调用一样传递额外的参数。谢谢@Enheh。:))

a-method( invocant: arg2, arg3, ... )
a-method invocant: arg2, arg3, ...

在设计文档中称为"间接对象表示法",这些格式是一种未记录且很少见的方法调用形式,其中调用模仿了方法声明——方法名称在前,然后是调用者,然后是冒号:

1
say first : Numeric ; # 2

请注意,say 是一个子调用,因为下一个标记 first 后面没有冒号。相比之下 first 是一个方法调用,因为它后面的标记后面跟着一个冒号。

脚注

1 此答案中有关空格/间距的所有评论都忽略了取消间距。


正如 Raiph 上面所说的,say: 是一个标签。所以你没有 say 任何东西(即使你认为你做了)并且——在使用 REPL 之外——编译器会抱怨你对 的使用是无用的:

1
say: ; # OUTPUT: ?WARNINGS for <tmp>:?Useless use of constant value a b c in sink context (lines 1, 1, 1, 1, 1, 1)??

但是,您通常可以在方法调用中使用 : 表示法而不是括号。考虑下面的四个例程调用(两个子例程调用,然后是两个方法调用):

1
2
3
4
5
my @numbers = (33, 77, 49, 11, 34);
say map  *.is-prime, @numbers  ;  # simplest subroutine call syntax
say map( *.is-prime, @numbers );  # same meaning, but delimiting args
say @numbers.map( *.is-prime ) ;  # similar, but using .map *method*
say @numbers.map: *.is-prime   ;  # same, but using : instead of parens

这些句子都将返回相同的 (False False False True False).

一般来说,正如你在上面看到的map,你可以在方法调用中使用(),只要你会使用:,但反之则不然; : 只能在方法调用中使用。

如果需要精确分隔参数,请使用 (),如下面的 Raiph 评论。

此答案侧重于基础知识。有关例程调用语法的精确细节的更详尽的介绍,请参阅 Raiph 的答案。 (作为一个重要的例子,如果例程名称和冒号 (:) 或左括号 (() 之间有空格,这些调用的含义通常会发生变化)。