函数可以通过以下几种方式调用:
1 2
| 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, ¬e 等等。还有一些方法调用的形式。
-
如果您将 say: 1, 2, 3;Nil 写入 REPL,它将显示 Nil
-
另请参阅stackoverflow.com/questions/30982697/...
@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 形式的后缀方法调用除了调用者(. 左侧的参数,如果没有显式调用者,则为当前主题)之外没有参数,那么你可以只需将其写成:
您可以选择附加一个冒号:
没有很好的理由,但它符合:
.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:
像往常一样使用 : 或括号将附加参数传递给 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 此答案中有关空格/间距的所有评论都忽略了取消间距。
-
"您不能以这种形式将其他参数传递给 bar。"但是,如果添加括号,则可以传递其他参数:foo.&bar(42,"baz")
-
我从来不知道间接对象语法,但它完全有道理非常酷。它允许您将任何对象的方法转换为伪命令式语法。 @foo.push: $a 是推送的面向对象形式,push @foo, $a 是命令形式,但后者需要定义一个全局子 push。 IO 语法允许您在任何方法上获得命令式样式,尽管第一个分隔符使用冒号而不是逗号:push @foo: $a。 (其实我觉得这个表格我用过几次都没有意识到,所以我会说它实际上是相当直观的)
-
@guifa我刚刚意识到间接语法模仿方法声明语法(并相应地编辑了我的答案)。这可能是它对您来说很直观的另一个重要原因。
-
此外,您给我们的示例也很有帮助。先说@foo:条件读起来很漂亮(说第一个 foo 就是条件),但是虽然许多内置方法都有子变体,但它不是通用的这种语法有效地使它如此。
-
"方法名称和左括号1之间不能有空格"我认为它应该在这里读取 : 而不是"括号"(感谢您的回答)。
-
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-w??hat)
-
哦 - 并且命令式 y $obj: $val 也呼应了使用冒号来描述调用者的签名 - method y ( $obj : $val ) {...} ...非常整洁!
-
1 of 2 @p6steve 我喜欢你的 $obj.y: 42; 成语有两个原因。 A) 它读起来很好。 B) 无需使用 Proxy 类即可编写副作用代码。我认为使用您的成语回答将是 2015 SO 的一个很好的补充。尽管提问者在他们的 Q 中写道他们想使用 obj.y = 语法,但他们也对他们接受的答案留下了"有点丑陋......很多样板"的评论。我认为他们和/或那个 Q 的读者可能更喜欢使用你的习语,因为它很漂亮并且避免了 Proxy。
-
2 of 2 该文档(以及您的用法,大概遵循该文档)与拉里关于该主题的"私人"/"公共"术语相矛盾。出于很好的理由,他说所有属性始终是私有的,所有访问者始终是公共的。 jnthn 试图传达的内容是相关的。对于良好的 OO 设计,特别是如果它们将在并发设置中使用,方法几乎不应该对应于属性。例如,如果时钟时间类具有小时和分钟属性,则不应有一种方法来更新小时,而另一种方法来更新分钟。
-
cher @raiph - 我们需要一个实用的术语来表示 has $.x; vs. has $!y; ... public attribute vs. private attribute 对于初学者和专家来说是最容易理解的[尽管专家知道"公共属性"省略了"私有属性"
正如 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 的答案。 (作为一个重要的例子,如果例程名称和冒号 (:) 或左括号 (() 之间有空格,这些调用的含义通常会发生变化)。
- 这个答案有很多错误。我尝试编辑答案,但它基本上拆除了原件,所以我只写了一个新答案并发布了它。
-
没有人想要有错误的答案。我会尝试修复它。感谢您的建议。
-
@raiph 试图修复一些东西......只是删除,字母越少,错误就越少。