Raku 从人类语言中借用了许多概念。这并不奇怪,因为它是由一位语言学家设计的。

它在不同的上下文中重复使用通用元素;它有名称(术语)和动词(运算符)的概念;它对上下文敏感(在日常意义上,不一定在计算机科学解释中),因此符号可以根据预期是名称还是动词而具有不同的含义。

它也是自时钟的,因此解析器可以检测到大多数常见错误并给出良好的错误消息。

词汇约定§

Raku 代码是 Unicode 文本。当前的实现支持 UTF-8 作为输入编码。

另请参阅 Unicode 与 ASCII 符号

自由形式§

Raku 代码也是自由形式的,从某种意义上说,你基本上可以自由选择使用多少空格,尽管在某些情况下,空格的存在或不存在具有意义。

所以你可以写

if True {
    say "Hello";
}

    if True {
say "Hello"# Bad indentation intended 
        }

if True { say "Hello" }

或甚至

if True {say "Hello"}

尽管你不能在最后一个示例中省略更多空格。

取消空格§

在编译器不允许空格的地方,你可以使用任意数量的空格,只要用反斜杠引用即可。但是,令牌中的取消空格不受支持。编译器生成行号时,未取消空格的新行仍然计数。取消空格的用例是分隔后缀运算符和例程参数列表。

sub alignment(+@l{ +@l };
sub long-name-alignment(+@l{ +@l };
alignment\         (1,2,3,4).say;
long-name-alignment(3,5)\   .say;
say Inf+Inf\i;

在这种情况下,我们的目的是让两个语句的 . 以及括号对齐,因此我们在用于填充的空格前加上一个 \

用分号分隔语句§

Raku 程序是一个语句列表,由分号 ; 分隔。

say "Hello";
say "world";

最终语句(或块内最终语句)后的分号是可选的。

say "Hello";
say "world"
if True {
    say "Hello"
}
say "world"

隐含分隔符规则(针对以块结尾的语句)§

如果块的闭合花括号 } 后同一行没有其他语句,则以裸块结尾的完整语句可以省略尾随分号。这称为“隐含分隔符规则”。例如,您不需要在 if 语句块后写分号,如上所示和如下所示。

if True { say "Hello" }
say "world";

但是,需要分号将块与同一行中的尾随语句分隔开。

if True { say "Hello" }say "world";
#                     ^^^ this ; is required 

除了可能以裸块结尾的控制语句外,此隐含语句分隔符规则还以其他方式应用。例如,与方法调用冒号 : 语法结合使用。

my @names = <Foo Bar Baz>;
my @upper-case-names = @names.map: { .uc }    # OUTPUT: [FOO BAR BAZ] 

对于作为同一 if/elsif/else(或类似)结构一部分的一系列块,隐含分隔符规则仅适用于该系列的最后一个块的末尾。这三个是等效的

if True { say "Hello" } else { say "Goodbye" }say "world";
#                                            ^^^ this ; is required 
if True { say "Hello" } else { say "Goodbye" } # <- implied statement separator 
say "world";
if True { say "Hello" }   # still in the middle of an if/else statement 
else    { say "Goodbye" } # <- no semicolon required because it ends in a block 
                          #    without trailing statements in the same line 
say "world";

注释§

注释是程序文本的一部分,仅供人类读者使用;Raku 编译器不会将其作为程序文本进行评估。它们是 非环境 代码的一部分,其中包括 Pod6 文本。

在空格的缺失或存在消除可能的解析歧义的地方,注释算作空格。

单行注释§

Raku 中最常见的注释形式以单个井号字符 # 开头,并一直持续到行尾。

if $age > 250 {     # catch obvious outliers 
    # this is another comment! 
    die "That doesn't look right"
}

多行/嵌入式注释§

多行和嵌入式注释以井号字符开头,后跟反引号,然后是 Unicode 开标点符号,并以匹配的闭标点符号结尾。反引号和标点符号之间不允许有空格;它将被视为单行注释。内容不仅可以跨越多行,还可以内联嵌入。

if #`( why would I ever write an inline comment here? ) True {
    say "something stupid";
}

这些注释可以扩展多行

#`[
And this is how a multi would work.
That says why we do what we do below.
]
say "No more";

注释内的花括号可以嵌套,因此在 #`{ a { b } c } 中,注释一直持续到字符串的最末尾;这就是为什么如果开括号字符也出现在注释正文中时,例如 #`[ This is a box [ of stuff ] ],它必须具有配对的闭合字符,如所示。您还可以使用多个花括号,例如 #`{{ double-curly-brace }},这可能有助于消除嵌套分隔符的歧义。只要您不将它们插入关键字或标识符的中间,就可以在表达式中嵌入这些注释。

Pod 注释§

Pod 语法可用于多行注释

say "this is code";
 
=begin comment
 
Here are several
lines
of comment
 
=end comment
 
say 'code again';

标识符§

标识符是语法构建块,可用于为实体/对象(如常量、变量(例如 Scalar)和例程(例如 Sub 和方法))命名。在 变量名 中,任何 sigil(和 twigil)都位于标识符之前,不构成标识符的一部分。

constant c = 299792458;     # identifier "c" names an Int 
my $a = 123;                # identifier "a" in the name "$a" of a Scalar 
sub hello { say "Hello!" }# identifier "hello" names a Sub

标识符有不同的形式:普通标识符、扩展标识符和复合标识符。

普通标识符§

普通标识符由一个字母开头,后面可以跟随一个或多个字母数字字符。它还可以包含孤立的嵌入式撇号 ' 和/或连字符 -,但下个字符每次都必须是字母。

“字母”和“字母数字”的定义包括适当的 Unicode 字符。哪些字符“适当”取决于实现。在 Rakudo/MoarVM Raku 实现中,字母字符包括 Unicode 通用类别值为字母 (L) 的字符和下划线 _。字母数字字符还包括 Unicode 通用类别值为数字、十进制数字 (Nd) 的字符。

# valid ordinary identifiers: 
x
_snake_oil
something-longer
with-numbers1234
don't-do-that
piece_of_π
駱駝道              # "Rakuda-dō", Japanese for "Way of the camel" 
# invalid ordinary identifiers: 
42                 # identifier does not start with alphabetic character 
with-numbers1234-5 # embedded hyphen not followed by alphabetic character 
is-prime?          # question mark is not alphanumeric 
x²                 # superscript 2 is not alphanumeric (explained above) 

扩展标识符§

通常,使用包含普通标识符中不允许的字符的名称很方便。用例包括实体集共享一个通用的“短”名称,但仍然需要其每个元素都能单独识别的情况。例如,你可以使用一个短名称为 Dog 的模块,而其长名称包括其命名机构和版本

Dog:auth<Somebody>:ver<1.0>  # long module names including author and version 
Dog:auth<Somebody>:ver<2.0>
 
use Dog:auth<Somebody>:ver<2.0>;
# Selection of second module causes its full name to be aliased to the 
# short name for the rest of # the lexical scope, allowing a declaration 
# like this. 
my Dog $spot .= new("woof");

类似地,运算符集在各种语法类别中协同工作,名称如 prefixinfixpostfix。这些运算符的正式名称通常包含普通标识符中排除的字符。长名称构成扩展标识符,并包括此语法类别;短名称将在定义中用引号引起来

infix:<+>                 # the official name of the operator in $a + $b 
infix:<*>                 # the official name of the operator in $a * $b 
infix:«<=»                # the official name of the operator in $a <= $b 

对于所有此类用途,你可以将一个或多个冒号分隔的字符串附加到普通标识符,以创建一个所谓的扩展标识符。当附加到标识符(即,在后缀位置)时,此冒号分隔的字符串会生成该标识符的唯一变体。

这些字符串具有形式 :key<value>,其中 key value 是可选的;也就是说,在将其与常规标识符分隔的冒号之后,将有一个 key 和/或一个引号括号结构,如 < >« »[' '],它引用一个或多个任意字符 value[1]

# exemplary valid extended identifiers: 
postfix:<²>               # the official long name of the operator in $x² 
WOW:That'sAwesome
WOW:That's<<🆒>>
party:sweet<16>
 
# exemplary invalid extended identifiers: 
party:16<sweet>           # 16 is not an ordinary identifier 
party:16sweet
party:!a                  # ...and neither is !a 
party:$a                  # ...nor $a 

在扩展标识符中,后缀字符串被视为名称的组成部分,因此infix:<+>infix:<->是两个不同的运算符。但是,使用的括号字符不计入其中;只有引号中的数据才算数。因此,这些都是相同的名称

infix:<+>
infix:<<+>>
infix:«+»
infix:['+']
infix:('+')

类似地,所有这些都可以工作

my $foo:bar<baz> = 'quux';
say $foo:bar«baz»;                               # OUTPUT: «quux␤» 
my $take-me:<home> = 'Where the glory has no end';
say $take-me:['home'];                           # OUTPUT: «Where [...]␤» 
my $foo:bar<2> = 5;
say $foo:bar(1+1);                               # OUTPUT: «5␤» 

当一个扩展标识符包含两个或更多冒号对时,它们的顺序通常很重要

my $a:b<c>:d<e> = 100;
my $a:d<e>:b<c> = 200;
say $a:b<c>:d<e>;               # OUTPUT: «100␤», NOT: «200␤» 

此规则的例外是模块版本控制;因此,这些标识符有效地命名了相同的模块

use ThatModule:auth<Somebody>:ver<2.7.18.28.18>
use ThatModule:ver<2.7.18.28.18>:auth<Somebody>

此外,扩展标识符支持编译时插值;这需要使用常量作为插值值

constant $c = 42;  # Constant binds to Int; $-sigil enables interpolation 
my $a:foo<42> = "answer";
say $a:foo«$c»;    # OUTPUT: «answer␤» 

尽管引用括号结构在标识符上下文中通常可以互换,但它们并不相同。特别是,尖括号< >(模仿单引号插值特性)不能用于常量名的插值。

constant $what = 'are';
my @we:<are>= <the champions>;
say @we:«$what»;     # OUTPUT: «[the champions]␤» 
say @we:<$what>;
# Compilation error: Variable '@we:<$what>' is not declared 

复合标识符§

复合标识符是由两个或更多普通和/或扩展标识符组成的标识符,这些标识符由双冒号::分隔。

双冒号::称为命名空间分隔符包分隔符,它阐明了其在名称中的语义功能:强制名称的前一部分被视为/命名空间,通过该包/命名空间可以找到名称的后一部分

module MyModule {               # declare a module package 
    our $var = "Hello";         # declare package-scoped variable 
}
say $MyModule::var              # OUTPUT: «Hello␤»

在上面的示例中,MyModule::var是一个复合标识符,由包名标识符MyModule和变量名var的标识符部分组成。总而言之,$MyModule::var通常称为包限定名称

用双冒号分隔标识符会导致最右边的名称插入到现有(见上例)或自动创建的包中

my $foo::bar = 1;
say OUR::.keys;           # OUTPUT: «(foo)␤» 
say OUR::foo.HOW          # OUTPUT: «Perl6::Metamodel::PackageHOW.new␤» 

最后一行显示了如何自动创建foo包,作为该命名空间中变量的存储库。

双冒号语法允许使用::($expr)将字符串运行时插值到包或变量名中,而通常情况下您会将包或变量名放在那里

my $buz = "quux";
my $bur::quux = 7;
say $bur::($buz);               # OUTPUT: «7␤»

术语 term:<>§

您可以使用term:<>来引入新术语,这对于引入不符合普通标识符规则的常量非常方便

use Testplan 1constant &term:<👍> = &ok.assuming(True);
👍
# OUTPUT: «1..1␤ok 1 - ␤»

但术语不必是常量:您还可以将它们用于不接受任何参数的函数,并强制解析器在它们之后期望一个运算符。例如

sub term:<dice> { (1..6).pick };
say dice + dice;

可以打印 2 到 12 之间的任何数字。

如果我们相反将dice声明为常规

sub dice() {(1...6).pick }

,表达式dice + dice将被解析为dice(+(dice())),导致错误,因为sub dice期望零个参数。

语句和表达式§

Raku 程序由语句列表组成。语句的一个特例是表达式,它返回一个值。例如if True { say 42 }在语法上是一个语句,但不是一个表达式,而1 + 2是一个表达式(因此也是一个语句)。

do前缀将语句转换为表达式。因此,虽然

my $x = if True { 42 };     # Syntax error! 

是一个错误,

my $x = do if True { 42 };

将 if 语句的返回值(此处为42)分配给变量$x

术语§

术语是基本名词,可以与运算符一起形成表达式。术语的示例包括变量($x)、类型名称等裸字(Int)、文字(42)、声明(sub f() { })和调用(f())。

例如,在表达式 2 * $salary 中,2$salary 是两个术语(Int 文字和变量)。

变量§

变量通常以一个称为标记的特殊字符开头,后跟一个标识符。在使用变量之前必须声明它们。

# declaration: 
my $number = 21;
# usage: 
say $number * 2;

有关更多详细信息,请参见变量文档

裸字(常量、类型名称)§

预声明的标识符可以单独成为术语。这些通常是类型名称或常量,但术语 self 也指调用方法的对象(请参见对象)和无标记变量

say Int;                # OUTPUT: «(Int)␤» 
#   ^^^ type name (built in) 
 
constant answer = 42;
say answer;
#   ^^^^^^ constant 
 
class Foo {
    method type-name {
        self.^name;
      # ^^^^ built-in term 'self' 
    }
}
say Foo.type-name;     # OUTPUT: «Foo␤» 
#   ^^^ type name

包和限定名称§

命名的实体(例如变量、常量、类、模块或子例程)是命名空间的一部分。名称的嵌套部分使用 :: 分隔层次结构。一些示例

$foo                # simple identifiers 
$Foo::Bar::baz      # compound identifiers separated by :: 
$Foo::($bar)::baz   # compound identifiers that perform interpolations 
Foo::Bar::bob(23)   # function invocation given qualified name 

有关更多详细信息,请参见包文档

文字§

文字是源代码中常量值的表示形式。Raku 具有几种内置类型的文字,例如 Str、几种数字类型、Pair 等。

字符串文字§

字符串文字用引号括起来

say 'a string literal';
say "a string literal\nthat interprets escape sequences";

请参见引用以获取更多选项,包括插值引用 q。Raku 在文字中使用标准转义字符:\0 \a \b \t \n \f \r \e,其含义与 ASCII 转义代码相同,在设计文档中指定。

say "🔔\a";  # OUTPUT: «🔔␇␤»

数字文字§

数字文字通常以十进制指定(如果需要,可以通过前缀 0d 以文字形式指定),除非前缀如 0x(十六进制,16 进制)、0o(八进制,8 进制)或 0b(二进制,2 进制)或以副词表示法中的显式进制(如 :16<A0>)另行指定。与其他编程语言不同,前导零表示 8 进制;相反,会发出编译时警告。

在所有文字格式中,可以使用下划线对数字进行分组,尽管它们不携带任何语义信息;以下文字都计算为相同数字

1000000
1_000_000
10_00000
100_00_00

Int 文字§

整数默认为有符号 10 进制,但可以使用其他进制。有关详细信息,请参见Int

# not a single literal, but unary - operator applied to numeric literal 2 
-2
12345
0xBEEF      # base 16 
0o755       # base 8 
:3<1201>    # arbitrary base, here base 3 

Rat 字面量§

Rat 字面量(有理数)非常常见,在许多其他语言中取代了十进制数或浮点数。整数除法也会产生 Rat

1.          # Error: A number must have at least one digit after the radix point 
1.0
3.14159
-2.5        # Not actually a literal, but still a Rat 
:3<21.0012> # Base 3 rational 
2/3         # Not actually a literal, but still a Rat 

Num 字面量§

在 e 后以十为底使用整数指数的科学记数法会产生 Num(浮点值)

1.e0        # error: A number must have at least one digit after the radix point 
1e0
6.022e23
1e-9
-2e48
2e2.5       # error 

Complex 字面量§

Complex 数写为虚数(仅附加后缀 i 的有理数)或实数和虚数的和

1.+2i       # error: A number must have at least one digit after the radix point 
1+2.i       # error: A number must have at least one digit after the radix point 
1+2i
6.123e5i    # note that this is 6.123e5 * i, not 6.123 * 10 ** (5i) 

对字面量§

Pair 由键和值组成,有两种基本形式来构造它们:key => 'value':key('value')

箭头对§

箭头对可以有表达式、字符串字面量或“裸标识符”,即在左侧不需要引号的具有普通标识符语法的字符串

like-an-identifier-ain't-it => 42
"key" => 42
('a' ~ 'b'=> 1

状语对(冒号对)§

没有显式值的简短形式

my $thing = 42;
:$thing                 # same as  thing => $thing 
:thing                  # same as  thing => True 
:!thing                 # same as  thing => False 

变量形式也适用于其他符号,如 :&callback:@elements。如果值是数字字面量,也可以用这种简短形式表示

:42thing            # same as  thing => 42 
:٤٢thing            # same as  thing => 42

如果您使用其他字母表,此顺序将颠倒

:٤٢ث              # same as   ث => ٤٢

thaa 字母在数字之前。

具有显式值的较长形式

:thing($value)              # same as  thing => $value 
:thing<quoted list>         # same as  thing => <quoted list> 
:thing['some''values']    # same as  thing => ['some', 'values'] 
:thing{=> 'b'}            # same as  thing => { a => 'b' } 

布尔字面量§

TrueFalse 是布尔字面量;它们始终以大写字母开头。

数组字面量§

一对方括号可以包围一个表达式以形成一个项目化的 Array 字面量;通常里面有一个逗号分隔的列表

say ['a''b'42].join(' ');   # OUTPUT: «a b 42␤» 
#   ^^^^^^^^^^^^^^ Array constructor

如果构造函数给定一个 Iterable,它将克隆并将其扁平化。如果您想要一个仅包含一个 Iterable 元素的 Array,请确保在其后使用逗号

my @a = 12;
say [@a].raku;  # OUTPUT: «[1, 2]␤» 
say [@a,].raku# OUTPUT: «[[1, 2],]␤»

Array 构造函数不会扁平化其他类型的元素。使用 Slip 前缀运算符 (|) 扁平化所需的元素

my @a = 12;
say [@a34].raku;  # OUTPUT: «[[1, 2], 3, 4]␤» 
say [|@a34].raku# OUTPUT: «[1, 2, 3, 4]␤»

List 类型可以使用 is 特征 在声明中从数组字面量声明显式创建,而无需从 Array 转换。

my @a is List = 12# a List, not an Array 
# wrong: creates an Array of Lists 
my List @a;

哈希字面量§

一个前导的关联符号和一对括号 %( ) 可以围绕一个 ListPair 形成一个 Hash 字面量;通常里面有一个逗号分隔的 ListPair。如果使用非对,则假定它是一个键,下一个元素是值。最常与简单的箭头对一起使用。

say %=> 3=> 23:foo:dog<cat>"french""fries" );
# OUTPUT: «a => 3, b => 23, dog => cat, foo => True, french => fries␤» 
 
say %(=> 73foo => "fish").keys.join(" ");   # OUTPUT: «a foo␤» 
#   ^^^^^^^^^^^^^^^^^^^^^^^^^ Hash constructor

在左手边赋值给一个 %-符号变量时,包围右手边 Pair 的符号和括号是可选的。

my %ages = fred => 23jean => 87ann => 4;

默认情况下,%( ) 中的键被强制转换为字符串。要使用非字符串键编写哈希,请使用带有冒号前缀 :{ } 的大括号分隔符

my $when = :{ (now=> "Instant", (DateTime.now=> "DateTime" };

请注意,对于对象作为键,您不能将非字符串键作为字符串访问

say :{ -1 => 410 => 421 => 43 }<0>;  # OUTPUT: «(Any)␤» 
say :{ -1 => 410 => 421 => 43 }{0};  # OUTPUT: «42␤»

实现 Associative 角色的特定类型,Map(包括 HashStash 子类)和 QuantHash(及其子类),可以在声明中使用 is 特征,从哈希字面量中显式创建,而无需强制转换

my %hash;                    # Hash 
my %hash is Hash;            # explicit Hash 
my %map is Map;              # Map 
my %stash is Stash;          # Stash 
 
my %quant-hash is QuantHash# QuantHash 
 
my %setty is Setty;          # Setty 
my %set is Set;              # Set 
my %set-hash is SetHash;     # SetHash 
 
my %baggy is Baggy;          # Baggy 
my %bag is Bag;              # Bag 
my %bag-hash is BagHash;     # BagHash 
 
my %mixy is Mixy;            # Mixy 
my %mix is Mix;              # Mix 
my %mix-hash is MixHash;     # MixHash

请注意,使用带有哈希符号的常规类型声明会创建一个类型化的哈希,而不是特定类型

# This is wrong: creates a Hash of Mixes, not Mix: 
my Mix %mix;
# Works with $ sigil: 
my Mix $mix;
# Can be typed: 
my Mix[Int$mix-of-ints;

正则表达式字面量§

一个 Regex 用斜杠声明,如 /foo/。请注意,此 // 语法是完整 rx// 语法的缩写。

/foo/          # Short version 
rx/foo/        # Longer version 
Q :regex /foo/ # Even longer version
 
my $r = /foo/; # Regexes can be assigned to variables

签名字面量§

除了在子程序和块声明中典型用法外,签名还可以独立用于模式匹配。独立签名以冒号开头声明

say "match!" if 5"fish" ~~ :(IntStr); # OUTPUT: «match!␤» 
 
my $sig = :(Int $aStr);
say "match!" if (5"fish"~~ $sig# OUTPUT: «match!␤» 
 
given "foo"42 {
  when :(StrStr{ "This won't match" }
  when :(StrInt $n where $n > 20{ "This will!" }
}

有关更多信息,请参阅 签名 文档。

声明§

变量声明§

my $x;                          # simple lexical variable 
my $x = 7;                      # initialize the variable 
my Int $x = 7;                  # declare the type 
my Int:D $x = 7;                # specify that the value must be defined (not undef) 
my Int $x where { $_ > 3 } = 7# constrain the value based on a function 
my Int $x where * > 3 = 7;      # same constraint, but using Whatever shorthand

有关其他范围(ourhas)的更多详细信息,请参阅 变量声明符和范围

可调用声明§

Raku 为多个 Callable 代码对象(即可以调用的代码对象,例如子程序)提供语法。具体来说,Raku 为子程序(单分派和多分派)、代码块和方法(同样,单分派和多分派)提供语法。

子程序声明§

子程序使用关键字 sub 创建,后跟一个可选名称、一个可选签名和一个代码块。子程序是词法作用域的,因此如果在声明时指定了名称,则可以在词法作用域中使用相同的名称来调用子程序。子程序是 Sub 类型的实例,可以赋值给任何容器。

sub {}
# The minimal legal subroutine declaration 
 
sub say-hello1 { say "Hello!" }
# A subroutine with a name 
 
sub say-hello2(Str $to-whom{ say "Hello $to-whom!" }
# A subroutine with a name and a signature

您可以将子程序分配给变量。

my &greet0 = sub { say "Hello!" }            # Unnamed sub assigned to &greet0 
my &greet1 = sub say-hello1 { say "Hello!" } # Named sub assigned to &greet1

多重分派子例程声明§

子例程可以声明为一个multi——也就是说,作为一个具有多个候选者的子例程,每个候选者都有不同的签名。多重分派子例程的创建方式几乎与单重分派子例程完全相同:使用关键字multi,后面可以跟关键字sub,再跟一个可选的名称、一个可选的签名和一个代码块。有关详细信息,请参见多重分派

上面定义的两个单重分派子例程greet0greet1可以组合成一个greetmulti,如下所示

multi greet { say "Hello!" }
#     ^^^ optional 
multi greet($name{ say "Hello $name!" }

您还可以选择在multi声明之前加上一个proto声明;proto声明一个签名,任何调用都必须符合该签名,然后才能分派给候选者。例如,此proto要求所有候选者至少接受一个位置参数

proto at-least1($|{*}
multi at-least1($first)          { note $first }
multi at-least1($first$second{ note "got two" }
multi at-least1($first:$named{ note "got named" }
 
# The following is legal to *declare* but can never be called 
multi at-least1 { note "Got none"}

块声明§

Block是可以调用的代码对象,类似于子例程,但用于小规模代码重用。Block 是通过{ }大括号内的代码块创建的,可以先用->后跟签名(或<->后跟rw(又称读写)块的签名)。如果没有提供签名,Block 可以通过使用带有^ twigilC: twigil|/language/variables#The_:_twigil>的占位符变量来隐式接受参数。既没有显式签名也没有隐式签名的 Block 的默认签名为$_

my &greet0 = { say "Hello!" }     # Block stored in &greet0 
my &greet1 = { say "Hello $_!" }  # Block with default signature 
my &greet2 = -> $to-whom { say "Hello $to-whom!" }  # Block with explicit signature 
my &greet3 = { say "Hello $^to-whom!" }             # Block with implicit positional 
my &greet4 = { say "Hello $:to-whom!" }             # Block with implicit named 
 
my &add1-in-place = <-> $a { ++$a }                 # Block with rw signature

虽然这样做不太习惯,但 Block 也可以存储在标量变量中

my $greet = { say "Hello!" }

方法声明§

Method是可以针对特定对象(称为方法的“调用者”)调用的代码对象。在类声明之外,方法通过关键字method声明,后面跟一个签名,再跟一个代码块。在方法签名中,方法的调用者后面跟一个:,而不是通常用于分隔参数的,。在类之外声明的方法必须存储在变量中才能使用。

my &m = method ($invocant: $arg1$arg2{ note }

此语法不常见——通常,方法在类中声明。在此上下文中,方法声明更类似于子例程声明:方法通过关键字method声明,后面跟一个名称,再跟一个可选的签名,再跟一个代码块。在类中定义的方法,该类作为它们的默认调用者,但可以覆盖该默认值(例如,约束调用者的确定性)。

class Greeter {
   method greet($to-whom)           { say "Hello $to-whom!" }
   method defined-greet(Greeter:D:{ say "Hello!" }
}

有关方法的更多详细信息,请参见方法

多重分派方法声明§

在类中,您可以使用与多重分派子例程语法非常相似的语法声明多重分派方法。与multi子例程一样,您还可以为多重分派方法声明一个proto

class Greeter {
      multi method greet           { say "Hello!" }
      multi method greet($to-whom{ say "Hello $to-whom!" }
 
      proto method at-least1($|)   {*}
      multi method at-least1($first{ note $first }
      # The following is legal to *declare* but can never be called 
      multi method at-least1         { note "Got none"}
}

语法_declaration" data-indexedheader="语法;单元">packagemoduleclassroleGrammar 声明§

有几种类型的包,每种类型都使用关键字、名称、一些可选特征和例程、方法或规则的主体进行声明。

package P { }
 
module M { }
 
class C { }
 
role R { }
 
grammar G { }

可以在单个文件中声明多个包。但是,可以在文件开头声明一个 unit 包(仅在注释或 use 语句之前),并且文件的其余部分将被视为包的主体。在这种情况下,不需要大括号。

unit module M;
# ... stuff goes here instead of in {}'s 

调用代码对象§

Raku 提供了用于调用例程/块和调用方法的标准语法。此外,Raku 还提供了备用语法,用于使用类似方法的语法调用例程,并使用类似例程的语法调用方法

调用例程或块§

可以通过其名称后跟其参数(可选地用 (…) 括起来)来调用词法声明的例程和分配给 & 符号变量的例程/块。或者,它们的名称前面可以加上 & 符号,但在这种情况下,函数参数周围的 (…) 是必需的,但可以加上一个可选的 .。(没有 (…)& 符号变量将函数引用为一个对象 - 即,不调用它)。因此,以下所有内容都使用无参数调用函数 foo

foo;
foo();
&foo();
&foo.();

如果例程或块分配给 $ 符号变量,那么只能使用括号调用它

my $foo = sub { note }
$foo();
$foo.();

有关调用例程/块的更多信息,请参阅 函数

调用方法§

通过引用类后跟 . 后跟方法名称,在该类的实例上调用在类中定义的方法。不带参数调用方法不需要括号。要提供参数,您必须用 (…) 将它们括起来,或在方法名称后跟一个 :。以下代码显示了上面描述的语法

class Person {
    has $.age = 0;
    has Str $.name = "Anon";
 
    multi method greet        { say "Hello, $.name()!" }
    multi method greet($name{ say "Hello, $name!" }
};
my $person = Person.new(:name<Jane>:age(98));
 
$person.greet;   # Calls greet method with 0 args 
$person.greet(); # Calls greet method with 0 args 
$person.greet('Nushi'); # Calls greet method with 1 arg 
$person.greet: 'Nushi'# Calls greet method with 1 arg 

使用 : 调用方法(优先级下降)§

请注意,最后一行中的语法导致方法将语句的其余部分视为其参数列表。(或者,换句话说,它降低了方法调用的优先级;因此,这里使用的 : 有时称为“优先级下降”)。以下代码说明了该更改的后果

    my $band = 'Foo Fighters';
    say $band.substr03 ).uc# OUTPUT: «FOO␤» 
    say $band.substr: 03  .uc# OUTPUT: «Foo␤» 
 

最后一行与上一行并不等价;相反,它与 $band.substr(0, 3.uc 相同——可能不是此处预期的。这是因为在第二个方法调用中,uc 方法是在“3”上调用的,而不是在最左边的 substr 的结果上调用的;换句话说,substr 方法优先于 uc 方法。

在主题上调用方法§

如果调用了一个没有调用者(即在 . 的左侧没有任何内容)的方法,那么它将使用当前 主题变量 作为其调用者。例如

given 'Foo Fighters' {
   say .substr: 03;  # OUTPUT: «Foo␤» 
}

类似方法的函数调用和类似函数的方法调用§

子例程可以用类似方法的语法调用——也就是说,你可以像调用方法一样(包括选择性地使用 :)在对象上调用一个函数,后跟一个点。唯一的限制是,你必须在函数名前加上 &。例如

sub greet($name:$excited = True{
    say "Hello $name" ~ ($excited ?? '!' !! '.')
}
greet "Maria";    # OUTPUT: «Hello Maria!␤» 
"Maria".&greet;   # OUTPUT: «Hello Maria!␤» 
"Maria".&greet(); # OUTPUT: «Hello Maria!␤» 
given "Maria" { .&greet } # OUTPUT: «Hello Maria!␤» 
 
"Maria".&greet(:!excited); # OUTPUT: «Hello Maria.␤» 
"Maria".&greet: :!excited# OUTPUT: «Hello Maria.␤»

类似地,方法可以用类似函数的语法调用——也就是说,你可以通过先提供方法名,然后提供方法的参数(包括其调用者)来调用方法。要做到这一点,只需将调用者作为第一个参数提供,后跟一个 :

class Person {
    has Str $.name;
 
    multi method greet        { say "Hello, $.name()!" }
    multi method greet($name{ say "Hello, $name!" }
};
my $person = Person.new(:name<Ruòxī>:age(11));
 
greet $person:;         # same as $person.greet; 
greet Person: 'Yasmin'# same as Person.greet('Yasmin');

由于存在类似方法的函数语法和类似函数的方法语法,因此明确给定语法形式调用的是方法还是子例程尤为重要。这个细节很容易被忽略,特别是因为在许多情况下,Raku 提供了同名的方法和子例程。

例如,以下简单示例对函数和方法调用都生成相同的输出。

say 42;  # Subroutine call 
42.&say# Still a subroutine call 
42.say;  # Method call 
say 42:# Also a method call

然而,即使同时存在子例程和方法,调用正确的子例程或方法也会产生很大的不同。为了看到这一点,请考虑 map,它同时存在于子例程和方法中,但方法期望其参数按不同的顺序排列

my @list = 1..9;
sub add1($a{ $a + 1 }
 
map &add1@list# Sub call; expects list last 
map @list: &add1# Method call; expects function last 
 
@list.map(&add1);  # Method call; expects function last 
&add1.&map(@list); # Sub call; expects list last

运算符§

有关大量详细信息,请参见 运算符

运算符是具有更重符号和可组合语法的函数。与其他函数一样,运算符可以进行多重分派以允许特定于上下文的用法。

运算符有五种类型(排列),每种类型接受一个或两个参数。

++$x           # prefix, operator comes before single input 
5 + 3          # infix, operator is between two inputs 
$x++           # postfix, operator is after single input 
<the blue sky> # circumfix, operator surrounds single input 
%foo<bar>      # postcircumfix, operator comes after first input and surrounds second 

元运算符§

运算符可以组合。一个常见的示例是将中缀(二元)运算符与赋值结合起来。你可以将赋值与任何二元运算符结合起来。

$x += 5     # Adds 5 to $x, same as $x = $x + 5 
$x min= 3   # Sets $x to the smaller of $x and 3, same as $x = $x min 3 
$x .= child # Equivalent to $x = $x.child 

将中缀运算符包装在 [ ] 中以创建一个新的约简运算符,该运算符作用于单个输入列表,从而产生单个值。

say [+] <1 2 3 4 5>;    # OUTPUT: «15␤» 
(((1 + 2+ 3+ 4+ 5 # equivalent expanded version

将中缀运算符包装在 « »(或 ASCII 等效项 << >>)中以创建一个新的超运算符,该运算符对两个列表进行成对操作。

say <1 2 3> «+» <4 5 6> # OUTPUT: «(5 7 9)␤»

箭头的方向指示当列表大小不相同时该做什么。

@a «+« @b # Result is the size of @b, elements from @a will be re-used 
@a »+» @b # Result is the size of @a, elements from @b will be re-used 
@a «+» @b # Result is the size of the biggest input, the smaller one is re-used 
@a »+« @b # Exception if @a and @b are different sizes 

你还可以用超运算符包装一元运算符。

say -« <1 2 3> # OUTPUT: «(-1 -2 -3)␤»
1 [↑] 从 Raku 语言版本 6.d 开始,带有 sym 作为 key 的冒号对(例如 :sym<foo>)保留以供将来可能使用。