【问题标题】:What's the difference these two function calling conventions?这两个函数调用约定有什么区别?
【发布时间】:2018-10-17 18:32:01
【问题描述】:

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

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

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

【问题讨论】:

  • say: 是一个标签,而不是函数调用。如果你写I'm-a-label: 1, 2, 3,你应该得到相同的结果。我猜你正在使用 REPL,如果你不使用say,它会显示输入表达式的值。
  • 哦,我不知道。为什么它仍然有效?
  • 它不起作用。如果您将两个版本分别放在他们的文件中并运行它们say(1, 2, 3) 会打印出“123”,而另一个则什么也不做。 Perl 对Useless use of constant integer x 3 的抱怨。当然,在 REPL 中,每个表达式结果都会被打印出来,所以它不再无用了。
  • 也就是说,写函数调用的方法确实有很多。在特定场景中,每一个都比其他的更方便或更美观。有say(...)(即在函数名后直接有一个左括号,没有中间空格)或say ...(即在函数名后至少有一个空格)或.(...) for &say, &note等等。还有几种方法调用的形式。
  • 如果您将say: 1, 2, 3;Nil 写入REPL,它将显示Nil

标签: function syntax raku


【解决方案1】:

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

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

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

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 cmets。

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

【讨论】:

  • 这个答案有很多错误。我尝试编辑答案,但它基本上破坏了原来的答案,所以我只写了一个新答案并发布了。
  • 没有人想要有错误的答案。我会尝试修复它。感谢您的建议。
  • @raiph 试图修复一些问题...只是删除,字母越少,错误就越少。
【解决方案2】:

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

foo: valuea, valueb, ...

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

而是以labelfoo: 开头的语句。

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

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

“无用使用”警告意味着 &lt;a b c&gt; 没有以有用的方式使用。 say: 没有对值列表做任何事情。它只是一个不做任何事情的标签。

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

.a-method:

如果postfix method call using the form .a-method 除了调用者之外没有参数(. 左侧的参数,如果没有显式调用者,则为 the current topic),那么您可以将其写成以下形式:

42.say ;

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

42.say: ;

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

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

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

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

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

say <abc 2 def ghi> .first: Numeric ; # 2

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

say .[1 + .first: Numeric] given <abc 2 def ghi> ; # ghi

冒号形式的方法调用的参数列表也由有效的statement modifier 结束,例如given

say .first: Numeric given <abc 2 def ghi> ; # 2

a-sub arg1, arg2, ...

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

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

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

用于方法和子调用的另一种常见形式是在方法或子名称之后立即使用括号来分隔参数。左括号必须紧跟,例程名称和( 之间不能有空格。

下面是.first 方法使用的括号:

say 1 + .first(Numeric) given <abc 2 def ghi> ; # 3

这样的优点是它可以说比使用外部括号更漂亮:

say 1 + (.first: Numeric) given <abc 2 def ghi> ; # 3

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

my @array = <abc 2 def ghi> ;
say "first number is &first(Numeric,@array)" ; # first number is 2

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

my @array = <abc 2 def ghi> ;
say "first number is @array.first(Numeric)" ; # first number is 2

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

my @array = <abc 2 def ghi> ;
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, ... ) ;

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

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

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

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

say .first: (Numeric) given <abc 2 def ghi> ; # 2

(Numeric) 是括号中的单个值,产生Numeric,因此该行与以下内容相同:

say .first: Numeric given <abc 2 def ghi> ; # 2

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

say .first: Numeric, :k given <abc 2 def ghi> ; # 1
say .first(Numeric, :k) given <abc 2 def ghi> ; # 1

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

say .first: (Numeric, :k) given <abc 2 def ghi> ; # Nil

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

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

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

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

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

同样,我的建议是改为:

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

或:

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

如果您打算传递多个参数,或者如果您希望将列表作为单个参数传递,那么:

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

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

a-sub : arrgha, arrghb, ...

对于方法表单,这会给您带来“困惑”的编译错误。

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

.&amp;a-sub

有一个调用表单可以让你调用一个声明为 sub 的例程——但使用.method 调用语法。以下将点左侧的“invocant”qux 作为第一个参数提供给名为a-sub 的子程序:

qux.&a-sub

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

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, ...

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

say first <abc 2 def ghi>: Numeric ; # 2

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

脚注

1此答案中有关空格/间距的所有 cmets 忽略 unspacing

【讨论】:

  • "您不能以这种形式将其他参数传递给bar。"但是,如果添加括号,则可以传递其他参数:foo.&amp;bar(42, "baz")
  • ++ 我从来不知道间接对象语法,但它完全有道理非常酷。它允许您将任何对象的方法转换为伪命令式语法。 @foo.push: $a是推送的OO形式,push @foo, $a是命令形式,但后者需要定义一个全局子push。 IO 语法允许您在 any 方法上获得命令式样式,尽管第一个分隔符使用冒号而不是逗号:push @foo: $a。 (其实我觉得这个表格我用过几次都没有意识到,所以我说它其实还挺直观的)
  • 你给我们的例子也是一个很好的例子,它可以提供帮助。 say first @foo: condition 读起来很漂亮(说第一个 foo 就是条件),但是虽然许多内置方法都有子变体,但它并不通用这种语法有效地使它如此。
  • "方法名和左括号之间不能有空格1" 我认为应该是:而不是"paranthesis"在这里(感谢您的回答)。
  • raku OO 模型支持 (i) 公共属性变体 has $.x is rw; 和 (ii) 使用 getter/setter 方法 has $!y; multi method y {$!y} multi method y ($val) {$!y=$val} 的私​​有属性变体。为了设置一个属性,我有两个非常相似的语法:(i)$obj.x = 41; 和(ii)$obj.y: 42;。恕我直言,方法冒号变体有助于保持调用私有属性的代码与调用公共属性的代码一样精简,从而鼓励严格封装。 (见p6steve.wordpress.com/2020/05/07/raku-objects-confusing-or-what
猜你喜欢
  • 2023-01-10
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 2013-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-05
相关资源
最近更新 更多