本文档旨在向您简要介绍 Raku 编程语言。
对于 Raku 新手来说,它应该可以帮助您入门。

本文档的某些部分引用了Raku 文档的其他(更完整、更准确)部分。如果您需要有关特定主题的更多信息,则应阅读它们。

在本文档中,您将找到大多数讨论主题的示例。为了更好地理解它们,请花时间重现所有示例。

许可证

本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。要查看此许可证的副本,请访问

贡献

如果您想为本文档做出贡献,请访问

反馈

欢迎所有反馈:[email protected]

如果您喜欢这项工作,请在Github上为该存储库点“星标”。

1. 简介

1.1. 什么是 Raku

Raku 是一种高级的、通用的、逐渐类型化的语言。Raku 是多范式的。它支持过程式、面向对象和函数式编程。

Raku 座右铭
  • TMTOWTDI(发音为 Tim Toady):条条大路通罗马。

1.2. 术语

  • Raku:是一种具有测试套件的语言规范。通过规范测试套件的实现被认为是 Raku。

  • Rakudo:是 Raku 的编译器。

  • Zef:是 Raku 模块安装器。

  • Rakudo Star:是一个捆绑包,其中包括 Rakudo、Zef、Raku 模块集合和文档。

1.3. 安装 Raku

https://rakubrew.org 是一个平台无关的环境管理器(类似于 Raku 的 pyenv)

Linux

要安装 Rakudo Star,请在终端中运行以下命令

mkdir ~/rakudo && cd $_
curl -LJO https://rakudo.perl5.cn/latest/star/src
tar -xzf rakudo-star-*.tar.gz
mv rakudo-star-*/* .
rm -fr rakudo-star-*

./bin/rstar install

echo "export PATH=$(pwd)/bin/:$(pwd)/share/perl6/site/bin:$(pwd)/share/perl6/vendor/bin:$(pwd)/share/perl6/core/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc

有关其他 Unix 选项,请访问 https://rakudo.perl5.cn/star/source

macOS

有四种选择

  • 按照安装 Linux 时列出的相同步骤操作

  • 使用 Homebrew 安装:brew install rakudo-star

  • 使用 MacPorts 安装:sudo port install rakudo

  • https://rakudo.perl5.cn/latest/star/macos 获取最新的安装程序(扩展名为 .dmg 的文件)

Windows
  1. 对于 64 位架构:从 https://rakudo.perl5.cn/latest/star/win 获取最新的安装程序(扩展名为 .msi 的文件)

  2. 安装后,请确保 C:\rakudo\bin 在 PATH 中

Docker
  1. 获取官方 Docker 镜像 docker pull rakudo-star

  2. 然后使用该镜像运行容器 docker run -it rakudo-star

1.4. 运行 Raku 代码

可以使用 REPL(读取-求值-打印循环)运行 Raku 代码。为此,请打开终端,在终端窗口中键入 raku,然后按 [Enter] 键。这将导致出现 > 提示符。接下来,键入一行代码并按 [Enter] 键。REPL 将打印出该行的值。然后,您可以键入另一行,或者键入 exit 并按 [Enter] 键退出 REPL。

或者,将代码写入文件,保存并运行它。建议 Raku 脚本的文件扩展名为 .raku。通过在终端窗口中键入 raku filename.raku 并按 [Enter] 键来运行文件。与 REPL 不同,这不会自动打印每行的结果:代码必须包含类似 say 的语句才能打印输出。

REPL 主要用于尝试特定的代码段,通常是一行代码。对于包含多行代码的程序,建议将其存储在文件中,然后运行它们。

也可以通过在命令行上键入 raku -e 'your code here' 并按 [Enter] 键来非交互式地尝试单行代码。

Rakudo Star 捆绑了一个行编辑器,可以帮助您充分利用 REPL。

如果您安装的是纯 Rakudo 而不是 Rakudo Star,那么您可能没有启用行编辑功能(使用向上和向下箭头查看历史记录,使用向左和向右箭头编辑输入,使用 Tab 键自动完成)。请考虑运行以下命令,您将一切就绪

  • zef install Linenoise 适用于 Windows、Linux 和 macOS

  • 如果您使用的是 Linux 并更喜欢 Readline 库,则可以使用 zef install Readline

1.5. 编辑器

由于大多数情况下我们将编写 Raku 程序并将其存储在文件中,因此我们应该有一个可以识别 Raku 语法的合适的文本编辑器。

社区经常使用以下编辑器

1.6. 你好,世界!

我们将从 hello world 仪式开始。

say 'hello world';

也可以写成

'hello world'.say;

1.7. 语法概述

Raku 是自由格式的:大多数情况下,您可以随意使用任意数量的空格,尽管在某些情况下空格具有含义。

语句通常是用分号分隔的单行代码

say "Hello" if True;
say "World" if False;

文件或代码块中的最后一条语句后不需要分号,但最好还是加上它们。

可以包含一组语句。用花括号括起语句以创建块

{
    say "First statement in the block.";
    say "Second statement in the block.";
}

表达式是一种特殊的语句,它返回一个值:1+2 将返回 3

表达式由运算符组成。

  • 变量:可以操作和更改的值。

  • 字面量:常量值,如数字或字符串。

运算符按类型分类

类型

说明

示例

前缀

在项之前。

++1

中缀

在项之间

1+2

后缀

在项之后

1++

环绕

围绕项

(1)

后环绕

在一个项之后,围绕另一个项

Array[1]

1.7.1. 标识符

标识符是在定义项时赋予它们的名称。

规则
  • 它们必须以字母字符或下划线开头。

  • 它们可以包含数字(第一个字符除外)。

  • 它们可以包含破折号或撇号(第一个和最后一个字符除外),前提是每个破折号或撇号的右侧都有一个字母字符。

有效

无效

var1

1var

var-one

var-1

var’one

var'1

var1_

var1'

_var

-var

命名约定
  • 驼峰式大小写:variableNo1

  • 烤肉串式大小写:variable-no1

  • 蛇形式大小写:variable_no1

您可以随意命名标识符,但最好始终采用一种命名约定。

使用有意义的名称将使您(和其他人)的编程生活更轻松。

  • var1 = var2 * var3 在语法上是正确的,但其目的并不明显。

  • monthly-salary = daily-rate * working-days 将是命名变量的更好方法。

1.7.2. 注释

注释是编译器忽略并用作注释的文本。

注释分为 3 种类型

  • 单行

    # This is a single line comment
  • 嵌入式

    say #`(This is an embedded comment) "Hello World."
  • 多行

    =begin comment
    This is a multi line comment.
    Comment 1
    Comment 2
    =end comment

1.7.3. 引号

字符串需要用双引号或单引号分隔。

始终使用双引号

  • 如果您的字符串包含撇号。

  • 如果您的字符串包含需要插值的变量。

say 'Hello World';   # Hello World
say "Hello World";   # Hello World
say "Don't";         # Don't
my $name = 'John Doe';
say 'Hello $name';   # Hello $name
say "Hello $name";   # Hello John Doe

2. 运算符

2.1. 常用运算符

下表列出了最常用的运算符。

运算符 类型 描述 示例 结果

+

中缀

加法

1 + 2

3

-

中缀

减法

3 - 1

2

*

中缀

乘法

3 * 2

6

**

中缀

3 ** 2

9

/

中缀

除法

3 / 2

1.5

div

中缀

整数除法(向下取整)

3 div 2

1

%

中缀

模数

7 % 4

3

%%

中缀

可整除性

6 %% 4

6 %% 3

gcd

中缀

最大公约数

6 gcd 9

3

lcm

中缀

最小公倍数

6 lcm 9

18

==

中缀

数值相等

9 == 7

!=

中缀

数值不相等

9 != 7

<

中缀

数值小于

9 < 7

>

中缀

数值大于

9 > 7

<=

中缀

数值小于或等于

7 <= 7

>=

中缀

数值大于或等于

9 >= 7

<=>

中缀

数值三向比较器

1 <=> 1.0

相同

1 <=> 2

更少

3 <=> 2

更多

eq

中缀

字符串相等

"John" eq "John"

ne

中缀

字符串不相等

"John" ne "Jane"

lt

中缀

字符串小于

"a" lt "b"

gt

中缀

字符串大于

"a" gt "b"

le

中缀

字符串小于或等于

"a" le "a"

ge

中缀

字符串大于或等于

"a" ge "b"

leg

中缀

字符串三向比较器

"a" leg "a"

相同

"a" leg "b"

更少

"c" leg "b"

更多

cmp

中缀

智能三向比较器

"a" cmp "b"

更少

3.5 cmp 2.6

更多

=

中缀

赋值

my $var = 7

将值 7 分配给变量 $var

~

中缀

字符串连接

9 ~ 7

97

"Hi " ~ "there"

Hi there

x

中缀

字符串复制

13 x 3

131313

"Hello " x 3

Hello Hello Hello

~~

中缀

智能匹配

2 ~~ 2

2 ~~ Int

"Raku" ~~ "Raku"

"Raku" ~~ Str

"enlightenment" ~~ /light/

“light”

++

前缀

递增

my $var = 2; ++$var;

将变量递增 1 并返回结果 3

后缀

递增

my $var = 2; $var++;

返回变量 2,然后将其递增

--

前缀

递减

my $var = 2; --$var;

将变量递减 1 并返回结果 1

后缀

递减

my $var = 2; $var--;

返回变量 2,然后将其递减

+

前缀

将操作数强制转换为数值

+"3"

3

+True

1

+False

0

-

前缀

将操作数强制转换为数值并返回其负值

-"3"

-3

-True

-1

-False

0

?

前缀

将操作数强制转换为布尔值

?0

?9.8

?"Hello"

?""

my $var; ?$var;

my $var = 7; ?$var;

!

前缀

将操作数强制转换为布尔值并返回其否定值

!4

..

中缀

范围构造函数

0..5

创建区间 [0, 5] 的范围 [1]

..^

中缀

范围构造函数

0..^5

创建区间 [0, 5) 的范围 [1]

^..

中缀

范围构造函数

0^..5

创建区间 (0, 5] 的范围 [1]

^..^

中缀

范围构造函数

0^..^5

创建区间 (0, 5) 的范围 [1]

^

前缀

范围构造函数

^5

与 0..^5 相同 创建区间 [0, 5) 的范围 [1]

…​

中缀

惰性列表构造函数

0…​9999

仅在请求时返回元素

|

前缀

扁平化

|(0..5)

(0 1 2 3 4 5)

|(0^..^5)

(1 2 3 4)

2.2. 反向运算符

在任何运算符之前添加 R 将会反转其操作数。

正常操作 结果 反转运算符 结果

2 / 3

0.666667

2 R/ 3

1.5

2 - 1

1

2 R- 1

-1

2.3. 归约运算符

归约运算符作用于值列表。它们是通过用方括号 [] 将运算符括起来形成的

正常操作 结果 归约运算符 结果

1 + 2 + 3 + 4 + 5

15

[+] 1,2,3,4,5

15

1 * 2 * 3 * 4 * 5

120

[*] 1,2,3,4,5

120

有关运算符的完整列表,包括其优先级,请访问 https://raku-docs.perl5.cn/language/operators

3. 变量

Raku 变量分为 3 类:标量、数组和哈希。

符号(拉丁语中的 Sign)是用于对变量进行分类的前缀字符。

  • $ 用于标量

  • @ 用于数组

  • % 用于哈希

本指南提供了一个简化的变量模型,适合学习 Raku 的基础知识。要更深入地了解变量,请参阅 https://raku-docs.perl5.cn/language/containers

3.1. 标量

标量保存一个值或引用。

# String
my $name = 'John Doe';
say $name;

# Integer
my $age = 99;
say $age;

可以对标量执行一组特定的操作,具体取决于它保存的值。

字符串
my $name = 'John Doe';
say $name.uc;
say $name.chars;
say $name.flip;
JOHN DOE
8
eoD nhoJ
有关适用于字符串的完整方法列表,请参阅 https://raku-docs.perl5.cn/type/Str
整数
my $age = 17;
say $age.is-prime;
True
有关适用于整数的完整方法列表,请参阅 https://raku-docs.perl5.cn/type/Int
有理数
my $age = 2.3;
say $age.numerator;
say $age.denominator;
say $age.nude;
23
10
(23 10)
有关适用于有理数的完整方法列表,请参阅 https://raku-docs.perl5.cn/type/Rat

3.2. 数组

数组是包含多个值的列表。

my @animals = 'camel','llama','owl';
say @animals;

如下例所示,可以对数组执行许多操作

波浪号 ~ 用于字符串连接。
脚本
my @animals = 'camel','vicuña','llama';
say "The zoo contains " ~ @animals.elems ~ " animals";
say "The animals are: " ~ @animals;
say "I will adopt an owl for the zoo";
@animals.push("owl");
say "Now my zoo has: " ~ @animals;
say "The first animal we adopted was the " ~ @animals[0];
@animals.pop;
say "Unfortunately the owl got away and we're left with: " ~ @animals;
say "We're closing the zoo and keeping one animal only";
say "We're going to let go: " ~ @animals.splice(1,2) ~ " and keep the " ~ @animals;
输出
The zoo contains 3 animals
The animals are: camel vicuña llama
I will adopt an owl for the zoo
Now my zoo has: camel vicuña llama owl
The first animal we adopted was the camel
Unfortunately the owl got away and we're left with: camel vicuña llama
We're closing the zoo and keeping one animal only
We're going to let go: vicuña llama and keep the camel
说明

.elems 返回数组中元素的数量。
.push() 向数组添加一个或多个元素。
我们可以通过指定数组中元素的位置 @animals[0] 来访问该元素。
.pop 从数组中移除最后一个元素并返回它。
.splice(a,b) 将从位置 a 开始移除 b 个元素。

3.2.1. 固定大小数组

基本数组的声明如下

my @array;

基本数组可以具有无限长度,因此称为自动扩展数组。
该数组将接受任意数量的值,没有任何限制。

相反,我们也可以创建固定大小的数组。
无法访问超出其定义大小的数组元素。

要声明固定大小的数组,请在其名称后面的方括号中指定其最大元素数

my @array[3];

此数组最多可以容纳 3 个值,索引从 0 到 2。

my @array[3];
@array[0] = "first value";
@array[1] = "second value";
@array[2] = "third value";

您将无法向此数组添加第四个值

my @array[3];
@array[0] = "first value";
@array[1] = "second value";
@array[2] = "third value";
@array[3] = "fourth value";
Index 3 for dimension 1 out of range (must be 0..2)

3.2.2. 多维数组

到目前为止,我们看到的数组都是一维的。
幸运的是,我们可以在 Raku 中定义多维数组。

my @tbl[3;2];

此数组是二维的。第一个维度最多可以有 3 个值,第二个维度最多可以有 2 个值。

可以将其视为 3x2 的网格。

my @tbl[3;2];
@tbl[0;0] = 1;
@tbl[0;1] = "x";
@tbl[1;0] = 2;
@tbl[1;1] = "y";
@tbl[2;0] = 3;
@tbl[2;1] = "z";
say @tbl
[[1 x] [2 y] [3 z]]
数组的可视化表示
[1 x]
[2 y]
[3 z]
有关完整的数组参考,请参阅 https://raku-docs.perl5.cn/type/Array

3.3. 哈希

哈希是一组键/值对。
my %capitals = 'UK','London','Germany','Berlin';
say %capitals;
另一种填充哈希的简洁方法
my %capitals = UK => 'London', Germany => 'Berlin';
say %capitals;

可以对哈希调用的一些方法是

脚本
my %capitals = UK => 'London', Germany => 'Berlin';
%capitals.push: (France => 'Paris');
say %capitals.kv;
say %capitals.keys;
say %capitals.values;
say "The capital of France is: " ~ %capitals<France>;
输出
(France Paris Germany Berlin UK London)
(France Germany UK)
(Paris Berlin London)
The capital of France is: Paris
说明

.push: (key => 'Value') 添加一个新的键/值对。
.kv 返回一个包含所有键和值的列表。
.keys 返回一个包含所有键的列表。
.values 返回一个包含所有值的列表。
我们可以通过指定其键 %hash<key> 来访问哈希中的特定值

有关完整的哈希参考,请参阅 https://raku-docs.perl5.cn/type/Hash

3.4. 类型

在前面的示例中,我们没有指定变量应该保存什么类型的值。

.WHAT 将返回变量中保存的值的类型。
my $var = 'Text';
say $var;
say $var.WHAT;

$var = 123;
say $var;
say $var.WHAT;

如您在上面的示例中所见,$var 中值的类型曾经是 (Str),然后是 (Int)。

这种编程风格称为动态类型。动态是指变量可以包含任何类型的值。

现在尝试运行以下示例
请注意变量名称前的 Int

my Int $var = 'Text';
say $var;
say $var.WHAT;

它将失败并返回此错误消息:在赋值给 $var 时类型检查失败;预期为 Int,但得到 Str

发生的情况是,我们事先指定了变量的类型应该是 (Int)。当我们尝试为其分配 (Str) 时,它失败了。

这种编程风格称为静态类型。静态是指变量类型在赋值之前定义,并且不能更改。

Raku 被归类为渐进类型;它允许静态动态类型。

数组和哈希也可以是静态类型的
my Int @array = 1,2,3;
say @array;
say @array.WHAT;

my Str @multilingual = "Hello","Salut","Hallo","您好","안녕하세요","こんにちは";
say @multilingual;
say @multilingual.WHAT;

my Str %capitals = UK => 'London', Germany => 'Berlin';
say %capitals;
say %capitals.WHAT;

my Int %country-codes = UK => 44, Germany => 49;
say %country-codes;
say %country-codes.WHAT;
以下是常用类型列表

您很可能永远不会使用前两种类型,但为了提供信息,这里还是列出了它们。

类型

描述

示例

结果

Mu

Raku 类型层次结构的根

Any

新类的默认基类以及大多数内置类的基类

Cool

可以互换地视为字符串或数字的值

my Cool $var = 31; say $var.flip; say $var * 2;

13 62

Str

字符串

my Str $var = "NEON"; say $var.flip;

NOEN

Int

整数(任意精度)

7 + 7

14

Rat

有理数(有限精度)

0.1 + 0.2

0.3

Bool

布尔值

!True

3.5. 自省

自省是获取有关对象属性(如其类型)的信息的过程。
在前面的示例中,我们使用 .WHAT 返回变量的类型。

my Int $var;
say $var.WHAT;    # (Int)
my $var2;
say $var2.WHAT;   # (Any)
$var2 = 1;
say $var2.WHAT;   # (Int)
$var2 = "Hello";
say $var2.WHAT;   # (Str)
$var2 = True;
say $var2.WHAT;   # (Bool)
$var2 = Nil;
say $var2.WHAT;   # (Any)

保存值的变量的类型与其值相关。
强声明的空变量的类型是声明它时使用的类型。
未进行强声明的空变量的类型为 (Any)
要清除变量的值,请为其分配 Nil

3.6. 作用域

在首次使用变量之前,需要声明它。

Raku 中使用了几种声明符。到目前为止,我们一直在使用 my

my $var=1;

my 声明符为变量提供了词法作用域。换句话说,该变量只能在其声明的同一个块中访问。

Raku 中的代码块由 { } 界定。如果没有找到代码块,则变量将在整个 Raku 脚本中可用。

{
  my Str $var = 'Text';
  say $var;   # is accessible
}
say $var;   # is not accessible, returns an error

由于变量只能在其定义的代码块中访问,因此相同的变量名可以在另一个代码块中使用。

{
  my Str $var = 'Text';
  say $var;
}
my Int $var = 123;
say $var;

3.7. 赋值与绑定

我们在前面的例子中已经看到了如何给变量赋值
赋值使用 = 运算符完成。

my Int $var = 123;
say $var;

我们可以更改分配给变量的值

赋值
my Int $var = 123;
say $var;
$var = 999;
say $var;
输出
123
999

另一方面,我们不能更改绑定到变量的值。
绑定使用 := 运算符完成。

绑定
my Int $var := 123;
say $var;
$var = 999;
say $var;
输出
123
Cannot assign to an immutable value
变量也可以绑定到其他变量
my $a;
my $b;
$b := $a;
$a = 7;
say $b;
$b = 8;
say $a;
输出
7
8

绑定变量是双向的。
$a := $b$b := $a 具有相同的效果。

有关变量的更多信息,请参阅 https://raku-docs.perl5.cn/language/variables

4. 函数和修改器

区分函数和修改器非常重要。
函数不会更改调用它们的对象的状态。
修改器会修改对象的状态。

脚本
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
my @numbers = [7,2,4,9,11,3];

@numbers.push(99);
say @numbers;      #1

say @numbers.sort; #2
say @numbers;      #3

@numbers.=sort;
say @numbers;      #4
输出
[7 2 4 9 11 3 99] #1
(2 3 4 7 9 11 99) #2
[7 2 4 9 11 3 99] #3
[2 3 4 7 9 11 99] #4
说明

.push 是一个修改器;它会更改数组的状态(#1)

.sort 是一个函数;它返回一个排序后的数组,但不会修改初始数组的状态

  • (#2)表明它返回了一个排序后的数组。

  • (#3)表明初始数组仍然未被修改。

为了强制函数充当修改器,我们使用 .= 而不是 . (#4)(脚本的第 9 行)

5. 循环和条件

Raku 有许多条件和循环结构。

5.1. if

代码仅在满足条件时运行;即表达式计算结果为 True

my $age = 19;

if $age > 18 {
  say 'Welcome'
}

在 Raku 中,我们可以反转代码和条件。
即使代码和条件已反转,也始终先评估条件。

my $age = 19;

say 'Welcome' if $age > 18;

如果不满足条件,我们可以使用以下方法指定要执行的备用块

  • else

  • elsif

# run the same code for different values of the variable
my $number-of-seats = 9;

if $number-of-seats <= 5 {
  say 'I am a sedan'
} elsif $number-of-seats <= 7 {
  say 'I am 7 seater'
} else {
  say 'I am a van'
}

5.2. unless

if 语句的否定版本可以使用 unless 编写。

以下代码

my $clean-shoes = False;

if not $clean-shoes {
  say 'Clean your shoes'
}

可以写成

my $clean-shoes = False;

unless $clean-shoes {
  say 'Clean your shoes'
}

Raku 中的否定使用 !not 完成。

使用 unless (condition) 而不是 if not (condition)

unless 不能有 else 子句。

5.3. with

with 的行为类似于 if 语句,但会检查变量是否已定义。

my Int $var=1;

with $var {
  say 'Hello'
}

如果您在没有为变量赋值的情况下运行代码,则不会发生任何事情。

my Int $var;

with $var {
  say 'Hello'
}

withoutwith 的否定版本。您应该能够将其与 unless 联系起来。

如果第一个 with 条件不满足,则可以使用 orwith 指定备用路径。
withorwith 可以与 ifelsif 进行比较。

5.4. for

for 循环迭代多个值。

my @array = 1,2,3;

for @array -> $array-item {
  say $array-item * 100
}

请注意,我们创建了一个迭代变量 $array-item,然后对每个数组项执行了 *100 操作。

5.5. given

given 是 Raku 中其他语言中 switch 语句的等效项,但功能更强大。

my $var = 42;

given $var {
    when 0..50 { say 'Less than or equal to 50'}
    when Int { say "is an Int" }
    when 42  { say 42 }
    default  { say "huh?" }
}

成功匹配后,匹配过程将停止。

或者,proceed 将指示 Raku 即使在成功匹配后也继续匹配。

my $var = 42;

given $var {
    when 0..50 { say 'Less than or equal to 50';proceed}
    when Int { say "is an Int";proceed}
    when 42  { say 42 }
    default  { say "huh?" }
}

5.6. loop

loop 是编写 for 循环的另一种方式。

实际上,loop 是在 C 系列编程语言中编写 for 循环的方式。

Raku 属于 C 系列语言。

loop (my $i = 0; $i < 5; $i++) {
  say "The current number is $i"
}
有关循环和条件的更多信息,请参阅 https://raku-docs.perl5.cn/language/control

6. 输入/输出

在 Raku 中,两个最常见的*输入/输出*接口是*终端*和*文件*。

6.1. 使用终端进行基本输入/输出

6.1.1. say

say 写入标准输出。它在末尾追加一个换行符。换句话说,以下代码

say 'Hello Mam.';
say 'Hello Sir.';

将写入 2 个不同的行。

6.1.2. print

另一方面,print 的行为类似于 say,但不会添加新行。

尝试用 print 替换 say 并比较结果。

6.1.3. get

get 用于从终端捕获输入。

my $name;

say "Hi, what's your name?";
$name = get;

say "Dear $name welcome to Raku";

当上述代码运行时,终端将等待您输入您的姓名。输入它,然后按 [Enter]。随后,它会向您问好。

6.1.4. prompt

promptprintget 的组合。

上面的例子可以这样写

my $name = prompt "Hi, what's your name? ";

say "Dear $name welcome to Raku";

6.2. 运行 Shell 命令

可以使用两个子例程来运行 shell 命令

  • run 运行外部命令而不涉及 shell

  • shell 通过系统 shell 运行命令。它取决于平台和 shell。所有 shell 元字符都由 shell 解释,包括管道、重定向、环境变量替换等。

如果您使用的是 Linux/macOS,请运行此命令
my $name = 'Neo';
run 'echo', "hello $name";
shell "ls";
如果您使用的是 Windows,请运行此命令
shell "dir";

echols 是 Linux 上常见的 shell 关键字
echo 将文本打印到终端(相当于 Raku 中的 say
ls 列出当前目录中的所有文件和文件夹

dir 等效于 Windows 上的 ls

6.3. 文件输入/输出

6.3.1. slurp

slurp 用于从文件中读取数据。

创建一个包含以下内容的文本文件

datafile.txt
John 9
Johnnie 7
Jane 8
Joanna 7
my $data = slurp "datafile.txt";
say $data;

6.3.2. spurt

spurt 用于将数据写入文件。

my $newdata = "New scores:
Paul 10
Paulie 9
Paulo 11";

spurt "newdatafile.txt", $newdata;

运行上述代码后,将创建一个名为 newdatafile.txt 的新文件。它将包含新的分数。

6.4. 处理文件和目录

Raku 可以列出目录的内容,而无需借助 shell 命令(例如,使用 ls)。

say dir;                # List files and folders in the current directory
say dir "/Documents";   # List files and folders in the specified directory

此外,您还可以创建和删除目录。

mkdir "newfolder";
rmdir "newfolder";

mkdir 创建一个新目录。
rmdir 删除一个空目录,如果目录不为空,则返回错误。

您还可以检查路径是否存在;它是文件还是目录

在您将运行以下脚本的目录中,创建一个空文件夹 folder123 和一个空的 raku 文件 script123.raku

say "script123.raku".IO.e;
say "folder123".IO.e;

say "script123.raku".IO.d;
say "folder123".IO.d;

say "script123.raku".IO.f;
say "folder123".IO.f;

IO.e 检查目录/文件是否存在。
IO.f 检查路径是否为文件。
IO.d 检查路径是否为目录。

Windows 用户可以使用 /\\ 来定义目录
C:\\rakudo\\bin
C:/rakudo/bin
有关 I/O 的更多信息,请参阅 https://raku-docs.perl5.cn/type/IO

7. 子例程

7.1. 定义

子例程(也称为 subs函数)是一种打包和重用功能的方法。

子例程定义以关键字 sub 开头。在定义之后,可以通过它们的句柄调用它们。
查看以下示例

sub alien-greeting {
  say "Hello earthlings";
}

alien-greeting;

前面的示例展示了一个不需要任何输入的子例程。

7.2. 签名

子例程可能需要输入。该输入由参数提供。子例程可以定义零个或多个参数。子例程定义的参数的数量和类型称为其签名

以下子例程接受一个字符串参数。

sub say-hello (Str $name) {
    say "Hello " ~ $name ~ "!!!!"
}
say-hello "Paul";
say-hello "Paula";

7.3. 多重分派

可以定义多个具有相同名称但签名不同的子例程。调用子例程时,运行时环境将根据提供的参数的数量和类型决定使用哪个版本。这种类型的子例程的定义方式与普通子例程相同,只是我们使用 multi 关键字而不是 sub

multi greet($name) {
    say "Good morning $name";
}
multi greet($name, $title) {
    say "Good morning $title $name";
}

greet "Johnnie";
greet "Laura","Mrs.";

7.4. 默认参数和可选参数

如果定义了一个子例程来接受一个参数,而我们在调用它时没有提供所需的 参数,它将失败。

Raku 为我们提供了定义具有以下内容的子例程的能力

  • 可选参数

  • 默认参数

可选参数是通过在参数名称后附加 ? 来定义的。

sub say-hello($name?) {
  with $name { say "Hello " ~ $name }
  else { say "Hello Human" }
}
say-hello;
say-hello("Laura");

如果用户不需要提供参数,则可以定义默认值。
这是通过在子例程定义中为参数赋值来完成的。

sub say-hello($name="Matt") {
  say "Hello " ~ $name;
}
say-hello;
say-hello("Laura");

7.5. 返回值

到目前为止,我们看到的所有子例程都做了一些事情 ——它们在终端上显示一些文本。

但是,有时我们会为了它的返回值而执行一个子例程,以便我们可以在程序流程的稍后阶段使用它。

如果允许函数运行到其块的末尾,则最后一条语句或表达式将决定返回值。

隐式返回
sub squared ($x) {
  $x ** 2;
}
say "7 squared is equal to " ~ squared(7);

为了清楚起见,最好明确地指定我们想要返回的内容。这可以使用 return 关键字来完成。

显式返回
sub squared ($x) {
  return $x ** 2;
}
say "7 squared is equal to " ~ squared(7);

7.5.1. 限制返回值

在前面的示例之一中,我们看到了如何限制接受的参数为特定类型。返回值也可以这样做。

要将返回值限制为特定类型,我们在签名中使用箭头符号 -->

指示返回类型
sub squared ($x --> Int) {
  return $x ** 2;
}
say "1.2 squared is equal to " ~ squared(1.2);

如果我们未能提供与类型约束匹配的返回值,则会引发错误。

Type check failed for return value; expected Int but got Rat (1.44)

类型约束不仅可以控制返回值的类型;它们还可以控制其定义性。

在前面的示例中,我们指定返回值应为 Int

我们还可以使用以下签名指定返回的 Int 应严格定义或未定义
-→ Int:D-→ Int:U

话虽如此,最好使用这些类型约束。
以下是先前示例的修改版本,该版本使用 :D 强制返回的 Int 为已定义。

sub squared ($x --> Int:D) {
  return $x ** 2;
}
say "1.2 squared is equal to " ~ squared(1.2);
有关子例程和函数的更多信息,请参阅 https://raku-docs.perl5.cn/language/functions

8. 函数式编程

在本章中,我们将介绍一些有助于函数式编程的功能。

8.1. 函数是一等公民

函数/子例程是一等公民

  • 它们可以作为参数传递

  • 它们可以从其他函数返回

  • 它们可以赋值给变量

一个很好的例子是 map 函数。
map 是一个高阶函数,它可以接受另一个函数作为参数。

脚本
my @array = <1 2 3 4 5>;
sub squared($x) {
  $x ** 2
}
say map(&squared,@array);
输出
(1 4 9 16 25)
说明

我们定义了一个名为 squared 的子例程,它接受一个参数并将该参数与其自身相乘。
接下来,我们使用 map(一个高阶函数),并为其提供了两个参数,即 squared 子例程和一个数组。
结果是数组中元素的平方列表。

请注意,在将子例程作为参数传递时,我们需要在其名称前添加 &

8.2. 匿名函数

匿名函数也称为lambda
匿名函数不绑定到标识符(它没有名称)。

让我们重写 map 示例,并使用匿名函数

my @array = <1 2 3 4 5>;
say map(-> $x {$x ** 2},@array);

请注意,我们没有声明 squared 子例程并将其作为参数传递给 map,而是在匿名子例程中将其定义为 -> $x {$x ** 2}

在 Raku 语境中,我们称这种表示法为尖括号块

尖括号块也可用于将函数分配给变量
my $squared = -> $x {
  $x ** 2
}
say $squared(9);

8.3. 链式调用

在 Raku 中,方法可以链接,因此您不需要将一个方法的结果作为参数传递给另一个方法。

举例说明:给定一个数组,您可能需要返回该数组的唯一值,并按从大到小的顺序排序。

这是一个非链式解决方案

my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array = reverse(sort(unique(@array)));
say @final-array;

在这里,我们对 @array 调用 unique,将结果作为参数传递给 sort,然后将该结果传递给 reverse

相比之下,使用链式方法,上面的示例可以改写为

my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array = @array.unique.sort.reverse;
say @final-array;

您已经可以看到,链式方法更易于阅读

8.4. Feed 运算符

feed 运算符,在某些函数式编程语言中称为管道,进一步说明了方法链接。

前向 Feed
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@array ==> unique()
       ==> sort()
       ==> reverse()
       ==> my @final-array;
say @final-array;
说明
Start with `@array` then return a list of unique elements
                    then sort it
                    then reverse it
                    then store the result in @final-array

请注意,方法调用的流程是自上而下的,从第一步到最后一步。

后向 Feed
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array-v2 <== reverse()
                   <== sort()
                   <== unique()
                   <== @array;
say @final-array-v2;
说明

后向 feed 类似于前向 feed,但方向相反。
方法调用的流程是自下而上的,从最后一步到第一步。

8.5. Hyper 运算符

超级运算符 >>. 将对列表的所有元素调用一个方法,并返回结果列表。

my @array = <0 1 2 3 4 5 6 7 8 9 10>;
sub is-even($var) { $var %% 2 };

say @array>>.is-prime;
say @array>>.&is-even;

使用超级运算符,我们可以调用 Raku 中已经定义的方法,例如 is-prime,它告诉我们一个数字是否是素数。
此外,我们可以定义新的子例程,并使用超级运算符调用它们。在这种情况下,我们必须在方法名称前加上 &;例如,&is-even

这非常实用,因为它使我们不必编写 for 循环来迭代每个值。

Raku 保证结果的顺序与原始列表的顺序相同。但是,不能保证 Raku 实际上会按列表顺序或在同一线程中调用方法。因此,请谨慎使用具有副作用的方法,例如 sayprint

8.6. 连接

连接是值的逻辑叠加。

在下面的示例中,1|2|3 是一个连接。

my $var = 2;
if $var == 1|2|3 {
  say "The variable is 1 or 2 or 3"
}

连接的使用通常会触发自动线程化;对每个连接元素执行操作,并将所有结果组合成一个新的连接并返回。

8.7. 惰性列表

惰性列表是一个惰性求值的列表。
惰性求值会延迟表达式的求值,直到需要时才进行,并通过将结果存储在查找表中来避免重复求值。

优点包括

  • 通过避免不必要的计算来提高性能

  • 能够构建潜在的无限数据结构

  • 能够定义控制流程

要构建惰性列表,我们使用中缀运算符 …​
惰性列表具有初始元素生成器终点

简单的惰性列表
my $lazylist = (1 ... 10);
say $lazylist;

初始元素是 1,终点是 10。没有定义生成器,因此默认生成器是后继者 (+1)
换句话说,这个惰性列表可以返回(如果请求)以下元素 (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

无限惰性列表
my $lazylist = (1 ... Inf);
say $lazylist;

此列表可以返回(如果请求)1 到无穷大之间的任何整数,换句话说,任何整数。

使用推导的生成器构建的惰性列表
my $lazylist = (0,2 ... 10);
say $lazylist;

初始元素是 0 和 2,终点是 10。没有定义生成器,但使用初始元素,Raku 将推断生成器是 (+2)
此惰性列表可以返回(如果请求)以下元素 (0, 2, 4, 6, 8, 10)

使用定义的生成器构建的惰性列表
my $lazylist = (0, { $_ + 3 } ... 12);
say $lazylist;

在此示例中,我们显式定义了一个包含在 { } 中的生成器
此惰性列表可以返回(如果请求)以下元素 (0, 3, 6, 9, 12)

使用显式生成器时,终点必须是生成器可以返回的值之一。
如果我们复制上面的示例,将终点设为 10 而不是 12,它将不会停止。生成器会跳过终点。

或者,您可以将 0 …​ 10 替换为 0 …​^ * > 10
您可以将其理解为:从 0 开始,直到第一个大于 10 的值(不包括它)

这不会停止生成器
my $lazylist = (0, { $_ + 3 } ... 10);
say $lazylist;
这将停止生成器
my $lazylist = (0, { $_ + 3 } ...^ * > 10);
say $lazylist;

8.8. 闭包

Raku 中的所有代码对象都是闭包,这意味着它们可以引用外部作用域中的词法变量。

sub generate-greeting {
    my $name = "John Doe";
    sub greeting {
      say "Good Morning $name";
    };
    return &greeting;
}
my $generated = generate-greeting;
$generated();

如果运行上面的代码,它将在终端上显示 早上好,张三
虽然结果相当简单,但这个例子有趣的是,内部子例程 greeting 在执行之前从外部子例程返回。

$generated 已成为一个闭包

闭包是一种特殊类型的对象,它结合了两个东西

  • 一个子例程

  • 创建该子例程的环境。

环境包含闭包创建时作用域内的所有局部变量。在本例中,$generated 是一个闭包,它包含了 greeting 子例程和创建闭包时存在的字符串 John Doe

让我们来看一个更有趣的例子。

sub greeting-generator($period) {
  return sub ($name) {
    return "Good $period $name"
  }
}
my $morning = greeting-generator("Morning");
my $evening = greeting-generator("Evening");

say $morning("John");
say $evening("Jane");

在本例中,我们定义了一个子例程 greeting-generator($period),它接受一个参数 $period 并返回一个新的子例程。它返回的子例程接受一个参数 $name 并返回构造的问候语。

基本上,greeting-generator 是一个子例程工厂。在本例中,我们使用 greeting-generator 创建了两个新的子例程,一个说 早上好,另一个说 晚上好

$morning$evening 都是闭包。它们共享相同的子例程体定义,但存储不同的环境。
$morning 的环境中,$periodMorning。在 $evening 的环境中,$periodEvening

9. 类和对象

在上一章中,我们学习了 Raku 如何促进函数式编程。
在本章中,我们将了解 Raku 中的面向对象编程。

9.1. 简介

面向对象编程是当今广泛使用的范例之一。
对象是变量和子例程的集合。
变量称为属性,子例程称为方法
属性定义状态,方法定义对象的行为

是创建对象的模板。

为了理解这种关系,请考虑以下示例

一个房间里有 4 个人

对象 ⇒ 4 个人

这 4 个人都是人类

⇒ 人类

他们有不同的姓名、年龄、性别和国籍

属性 ⇒ 姓名、年龄、性别、国籍

面向对象的说法中,我们说对象是类的实例

考虑以下脚本

class Human {
  has $.name;
  has $.age;
  has $.sex;
  has $.nationality;
}

my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
say $john;

class 关键字用于定义类。
has 关键字用于定义类的属性。
.new() 方法称为构造函数。它创建作为调用它的类的实例的对象。

在上面的脚本中,新变量 $john 持有一个对 Human.new() 定义的“Human”新实例的引用。
传递给 .new() 方法的参数用于设置底层对象的属性。

可以使用 my 为类提供词法作用域

my class Human {

}

9.2. 封装

封装是一种面向对象的思想,它将一组数据和方法捆绑在一起。
对象中的数据(属性)应该是私有的,换句话说,只能从对象内部访问。
为了从对象外部访问属性,我们使用称为访问器的方法。

以下两个脚本具有相同的结果。

直接访问变量
my $var = 7;
say $var;
封装
my $var = 7;
sub sayvar {
  $var;
}
say sayvar;

方法 sayvar 是一个访问器。它允许我们访问变量的值,而无需直接访问它。

Raku 中通过使用twigils 来促进封装。
Twigils 是次要的符号。它们位于符号和属性名称之间。
类中使用两种 twigils

  • ! 用于显式声明属性是私有的。

  • . 用于自动生成属性的访问器。

默认情况下,所有属性都是私有的,但始终使用 ! twigil 是一个好习惯。

因此,我们应该将上面的类重写为

class Human {
  has $!name;
  has $!age;
  has $!sex;
  has $!nationality;
}

my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
say $john;

将以下语句附加到脚本:say $john.age;
它将返回此错误:Method 'age' not found for invocant of class 'Human',因为 $!age 是私有的,只能在对象内部使用。尝试在对象外部访问它将返回错误。

现在将 has $!age 替换为 has $.age 并观察 say $john.age; 的结果

9.3. 命名参数与位置参数

在 Raku 中,所有类都继承默认的 .new() 构造函数。
它可以通过向其提供参数来创建对象。
默认构造函数只能提供命名参数
在我们上面的示例中,请注意提供给 .new() 的参数是按名称定义的

  • name => 'John'

  • age => 23

如果我不想每次创建对象时都提供每个属性的名称怎么办?
然后我需要创建另一个接受位置参数的构造函数。

class Human {
  has $.name;
  has $.age;
  has $.sex;
  has $.nationality;
  # new constructor that overrides the default one.
  method new ($name,$age,$sex,$nationality) {
    self.bless(:$name,:$age,:$sex,:$nationality);
  }
}

my $john = Human.new('John',23,'M','American');
say $john;

9.4. 方法

9.4.1. 简介

方法是对象的子例程
与子例程一样,它们是打包一组功能的一种方式,它们接受参数,具有签名,并且可以定义为

方法使用 method 关键字定义。
通常情况下,需要使用方法对对象的属性执行某种操作。这强化了封装的概念。对象属性只能在对象内部使用方法进行操作。外部世界只能与对象方法交互,而不能直接访问其属性。

class Human {
  has $.name;
  has $.age;
  has $.sex;
  has $.nationality;
  has $.eligible;
  method assess-eligibility {
      if self.age < 21 {
        $!eligible = 'No'
      } else {
        $!eligible = 'Yes'
      }
  }

}

my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
$john.assess-eligibility;
say $john.eligible;

在类中定义方法后,可以使用“点符号”在对象上调用它们。
对象 . 方法 或如上例所示: $john.assess-eligibility

在方法的定义中,如果我们需要引用对象本身来调用另一个方法,我们使用 self 关键字。

在方法的定义中,如果我们需要引用一个属性,即使它是用 . 定义的,我们也使用 !
其理由是 . twigil 所做的是使用 ! 声明一个属性,并自动创建一个访问器。

在上面的例子中,if self.age < 21if $!age < 21 具有相同的效果,尽管它们在技术上是不同的。

  • self.age 调用 .age 方法(访问器)。
    可以写成 $.age

  • $!age 是对变量的直接调用。

9.4.2. 私有方法

普通方法可以在类外部的对象上调用。

**私有方法**是只能从类内部调用的方法。
一个可能的用例是一个方法调用另一个方法来执行特定操作。与外部世界交互的方法是公共的,而被引用的方法应该是私有的。我们不希望用户直接调用它,因此我们将其声明为私有的。

声明私有方法需要在其名称前使用 ! twigil。
私有方法使用 ! 而不是 . 调用。

method !iamprivate {
  # code goes in here
}

method iampublic {
  self!iamprivate;
  # do additional things
}

9.5. 类属性

**类属性**是属于类本身而不是其对象的属性。
它们可以在定义期间初始化。
类属性使用 my 而不是 has 声明。
它们是在类本身上而不是在其对象上调用的。

class Human {
  has $.name;
  my $.counter = 0;
  method new($name) {
    Human.counter++;
    self.bless(:$name);
  }
}
my $a = Human.new('a');
my $b = Human.new('b');

say Human.counter;

9.6. 访问类型

到目前为止,我们看到的所有例子都使用访问器从对象的属性中**获取**信息。

如果我们需要修改属性的值怎么办?
我们需要使用关键字 is rw 将其标记为“读/写”。

class Human {
  has $.name;
  has $.age is rw;
}
my $john = Human.new(name => 'John', age => 21);
say $john.age;

$john.age = 23;
say $john.age;

默认情况下,所有属性都声明为“只读”,但您可以使用 is readonly 显式地执行此操作。

9.7. 继承

9.7.1. 简介

**继承**是面向对象编程的另一个概念。

在定义类时,我们很快就会意识到一些属性/方法是许多类共有的。
我们应该复制代码吗?
不!我们应该使用**继承**。

假设我们要定义两个类,一个是人类的类,一个是雇员的类。
人类有两个属性:姓名和年龄。
雇员有四个属性:姓名、年龄、公司和工资。

人们可能会尝试将类定义为

class Human {
  has $.name;
  has $.age;
}

class Employee {
  has $.name;
  has $.age;
  has $.company;
  has $.salary;
}

虽然技术上正确,但上面的代码在概念上被认为是糟糕的。

更好的写法是

class Human {
  has $.name;
  has $.age;
}

class Employee is Human {
  has $.company;
  has $.salary;
}

is 关键字定义继承。
在面向对象的术语中,我们说 Employee 是 Human 的**子类**,而 Human 是 Employee 的**父类**。

所有子类都继承父类的属性和方法,因此无需重新定义它们。

9.7.2. 覆盖

类继承其父类的所有属性和方法。
在某些情况下,我们需要子类中的方法的行为与其继承的方法不同。
为此,我们在子类中重新定义该方法。
这个概念叫做**覆盖**。

在下面的例子中,方法 introduce-yourself 由 Employee 类继承。

class Human {
  has $.name;
  has $.age;
  method introduce-yourself {
    say 'Hi I am a human being, my name is ' ~ self.name;
  }
}

class Employee is Human {
  has $.company;
  has $.salary;
}

my $john = Human.new(name =>'John', age => 23,);
my $jane = Employee.new(name =>'Jane', age => 25, company => 'Acme', salary => 4000);

$john.introduce-yourself;
$jane.introduce-yourself;

覆盖的工作原理如下:

class Human {
  has $.name;
  has $.age;
  method introduce-yourself {
    say 'Hi I am a human being, my name is ' ~ self.name;
  }
}

class Employee is Human {
  has $.company;
  has $.salary;
  method introduce-yourself {
    say 'Hi I am a employee, my name is ' ~ self.name ~ ' and I work at: ' ~ self.company;
  }

}

my $john = Human.new(name =>'John',age => 23,);
my $jane = Employee.new(name =>'Jane',age => 25,company => 'Acme',salary => 4000);

$john.introduce-yourself;
$jane.introduce-yourself;

根据对象的所属类,将调用正确的方法。

9.7.3. 子方法

**子方法**是一种不被子类继承的方法。
它们只能从声明它们的类中访问。
它们使用 submethod 关键字定义。

9.8. 多重继承

Raku 允许多重继承。一个类可以继承自多个其他类。

class bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

class line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart is bar-chart is line-chart {
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
输出
Actual sales:
[10 9 11 8 7 10]
Forecast sales:
[9 8 10 7 6 9]
Actual vs Forecast:
[10 9 11 8 7 10]
说明

combo-chart 类应该能够容纳两个序列,一个用于绘制在条形图上的实际值,另一个用于绘制在线条图上的预测值。
这就是为什么我们将它定义为 line-chartbar-chart 的子类。
您应该已经注意到,在 combo-chart 上调用 plot 方法并没有产生预期的结果。只绘制了一个序列。
为什么会发生这种情况?
combo-chart 继承自 line-chartbar-chart,并且它们都有一个名为 plot 的方法。当我们在 combo-chart 上调用该方法时,Raku 内部机制将尝试通过调用其中一个继承的方法来解决冲突。

更正

为了使其正常运行,我们应该在 combo-chart 中重写 plot 方法。

class bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

class line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart is bar-chart is line-chart {
  method plot {
    say @.bar-values;
    say @.line-values;
  }
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
输出
Actual sales:
[10 9 11 8 7 10]
Forecast sales:
[9 8 10 7 6 9]
Actual vs Forecast:
[10 9 11 8 7 10]
[9 8 10 7 6 9]

9.9. 角色

**角色**类似于类,因为它们是属性和方法的集合。

角色使用关键字 role 声明。希望实现角色的类使用 does 关键字来实现。

让我们使用角色重写多重继承示例
role bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

role line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart does bar-chart does line-chart {
  method plot {
    say @.bar-values;
    say @.line-values;
  }
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;

运行上面的脚本,您将看到结果相同。

到目前为止,您可能会问自己:如果角色的行为像类,那么它们有什么用?
为了回答您的问题,请修改第一个用于展示多重继承的脚本,即我们*忘记*重写 plot 方法的脚本。

role bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

role line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart does bar-chart does line-chart {
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
输出
===SORRY!===
Method 'plot' must be resolved by class combo-chart because it exists in multiple roles (line-chart, bar-chart)
说明

如果将多个角色应用于同一个类并且存在冲突,则会引发编译时错误。
这是一种比多重继承更安全的方法,在多重继承中,冲突不被视为错误,而是在运行时简单地解决。

角色会警告您存在冲突。

9.10. 自省

**自省**是获取有关对象的信息的过程,例如其类型、属性或方法。

class Human {
  has Str $.name;
  has Int $.age;
  method introduce-yourself {
    say 'Hi I am a human being, my name is ' ~ self.name;
  }
}

class Employee is Human {
  has Str $.company;
  has Int $.salary;
  method introduce-yourself {
    say 'Hi I am a employee, my name is ' ~ self.name ~ ' and I work at: ' ~ self.company;
  }
}

my $john = Human.new(name =>'John',age => 23,);
my $jane = Employee.new(name =>'Jane',age => 25,company => 'Acme',salary => 4000);

say $john.WHAT;
say $jane.WHAT;
say $john.^attributes;
say $jane.^attributes;
say $john.^methods;
say $jane.^methods;
say $jane.^parents;
if $jane ~~ Human {say 'Jane is a Human'};

自省由以下内容提供便利:

  • .WHAT - 返回创建对象的类

  • .^attributes - 返回对象的所有属性

  • .^methods - 返回可以在对象上调用的所有方法

  • .^parents - 返回对象的父类

  • ~~ 被称为智能匹配运算符。如果对象是从与其进行比较的类或其任何继承类创建的,则其计算结果为 *True*。

有关 Raku 中面向对象编程的更多信息,请参阅

10. 异常处理

10.1. 捕获异常

**异常**是在运行时出现问题时发生的一种特殊行为。
我们说异常被*抛出*。

请考虑以下正确运行的脚本

my Str $name;
$name = "Joanna";
say "Hello " ~ $name;
say "How are you doing today?"
输出
Hello Joanna
How are you doing today?

现在考虑这个抛出异常的脚本

my Str $name;
$name = 123;
say "Hello " ~ $name;
say "How are you doing today?"
输出
Type check failed in assignment to $name; expected Str but got Int
   in block <unit> at exceptions.raku:2

请注意,每当发生错误时(在本例中,为字符串变量分配一个数字),程序都会停止,并且不会评估其他代码行。

**异常处理**是*捕获*已*抛出*的异常的过程,以便脚本继续工作。

my Str $name;
try {
  $name = 123;
  say "Hello " ~ $name;
  CATCH {
    default {
      say "Can you tell us your name again, we couldn't find it in the register.";
    }
  }
}
say "How are you doing today?";
输出
Can you tell us your name again, we couldn't find it in the register.
How are you doing today?

异常处理是通过使用 try-catch 块完成的。

try {
  # code goes in here
  # if anything goes wrong, the script will enter the below CATCH block
  # if nothing goes wrong, the CATCH block will be ignored
  CATCH {
    default {
      # the code in here will be evaluated only if an exception has been thrown
    }
  }
}

CATCH 块的定义方式与 given 块的定义方式相同。这意味着我们可以*捕获*并以不同的方式处理多种类型的异常。

try {
  # code goes in here
  # if anything goes wrong, the script will enter the below CATCH block
  # if nothing goes wrong, the CATCH block will be ignored
  CATCH {
    when X::AdHoc   { # do something if exception of type X::AdHoc is thrown }
    when X::IO      { # do something if exception of type X::IO is thrown }
    when X::OS      { # do something if exception of type X::OS is thrown }
    default         { # do something if exception is thrown and doesn't belong to the above types }
  }
}

10.2. 抛出异常

Raku 还允许您显式抛出异常。
可以抛出两种类型的异常

  • 临时异常

  • 类型化异常

临时
my Int $age = 21;
die "Error !";
类型化
my Int $age = 21;
X::AdHoc.new(payload => 'Error !').throw;

临时异常是使用 die 子例程抛出的,后跟异常消息。

类型化异常是对象,因此在上面的示例中使用了 .new() 构造函数。
所有类型化异常都派生自类 X,以下是一些示例
X::AdHoc 是最简单的异常类型
X::IO 与 IO 错误相关
X::OS 与操作系统错误相关
X::Str::Numeric 与尝试将字符串强制转换为数字相关

有关异常类型及其关联方法的完整列表,请访问 https://raku-docs.perl5.cn/type-exceptions.html

11. 正则表达式

正则表达式(或简称 *regex*)是用于模式匹配的字符序列。
将其视为一种模式。

if 'enlightenment' ~~ m/ light / {
    say "enlightenment contains the word light";
}

在本例中,智能匹配运算符 ~~ 用于检查字符串(enlightenment)是否包含单词(light)。
将“Enlightenment”与正则表达式 m/ light / 进行匹配

11.1. 正则表达式定义

可以像这样定义正则表达式

  • /light/

  • m/light/

  • rx/light/

除非明确指定,否则将忽略空格;m/light/m/ light / 相同。

11.2. 匹配字符

字母数字字符和下划线 _ 按原样写入。
所有其他字符必须使用反斜杠转义或用引号括起来。

反斜杠
if 'Temperature: 13' ~~ m/ \: / {
    say "The string provided contains a colon :";
}
单引号
if 'Age = 13' ~~ m/ '=' / {
    say "The string provided contains an equal character = ";
}
双引号
if '[email protected]' ~~ m/ "@" / {
    say "This is a valid email address because it contains an @ character";
}

11.3. 匹配字符类别

字符可以分为几类,我们可以根据它们进行匹配。
我们也可以匹配该类别的反面(除它之外的所有内容)。

类别

正则表达式

反面

正则表达式

单词字符(字母、数字或下划线)

\w

除单词字符以外的任何字符

\W

数字

\d

除数字以外的任何字符

\D

空格

\s

除空格以外的任何字符

\S

水平空格

\h

除水平空格以外的任何字符

\H

垂直空格

\v

除垂直空格以外的任何字符

\V

制表符

\t

除制表符以外的任何字符

\T

换行符

\n

除换行符以外的任何字符

\N

if "John123" ~~ / \d / {
  say "This is not a valid name, numbers are not allowed";
} else {
  say "This is a valid name"
}
if "John-Doe" ~~ / \s / {
  say "This string contains whitespace";
} else {
  say "This string doesn't contain whitespace"
}

11.4. Unicode 属性

如前一节所示,根据字符类别进行匹配很方便。
也就是说,更系统的方法是使用 Unicode 属性。
这允许您匹配 ASCII 标准内部和外部的字符类别。
ASCII 标准。
Unicode 属性用 <: > 括起来

if "Devanagari Numbers १२३" ~~ / <:N> / {
  say "Contains a number";
} else {
  say "Doesn't contain a number"
}
if "Привет, Иван." ~~ / <:Lu> / {
  say "Contains an uppercase letter";
} else {
  say "Doesn't contain an upper case letter"
}
if "John-Doe" ~~ / <:Pd> / {
  say "Contains a dash";
} else {
  say "Doesn't contain a dash"
}

11.5. 通配符

通配符也可以在正则表达式中使用。

. 表示任何单个字符。

if 'abc' ~~ m/ a.c / {
    say "Match";
}
if 'a2c' ~~ m/ a.c / {
    say "Match";
}
if 'ac' ~~ m/ a.c / {
    say "Match";
} else {
    say "No Match";
}

11.6. 量词

量词位于字符之后,用于指定我们期望它出现多少次。

问号 ? 表示零次或一次。

if 'ac' ~~ m/ a?c / {
    say "Match";
} else {
    say "No Match";
}
if 'c' ~~ m/ a?c / {
    say "Match";
} else {
    say "No Match";
}

星号 * 表示零次或多次。

if 'az' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaz' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}
if 'z' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}

+ 表示至少一次。

if 'az' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaz' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}
if 'z' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}

11.7. 匹配结果

每当将字符串与正则表达式匹配的过程成功时,匹配结果都会存储在特殊变量 $/ 中。

脚本
if 'Rakudo is a Perl 6 compiler' ~~ m/:s Perl 6/ {
    say "The match is: " ~ $/;
    say "The string before the match is: " ~ $/.prematch;
    say "The string after the match is: " ~ $/.postmatch;
    say "The matching string starts at position: " ~ $/.from;
    say "The matching string ends at position: " ~ $/.to;
}
输出
The match is: Perl 6
The string before the match is: Rakudo is a
The string after the match is: compiler
The matching string starts at position: 12
The matching string ends at position: 18
说明

$/ 返回一个*匹配对象*(与正则表达式匹配的字符串)。
可以在*匹配对象*上调用以下方法
.prematch 返回匹配之前的字符串。
.postmatch 返回匹配之后的字符串。
.from 返回匹配的起始位置。
.to 返回匹配的结束位置。

默认情况下,正则表达式定义中的空格将被忽略。
如果我们想匹配包含空格的正则表达式,我们必须明确地这样做。
正则表达式 m/:s Perl 6/ 中的 :s 强制考虑空格。
或者,我们可以将正则表达式写成 m/ Perl\s6 / 并使用 \s 来表示空格。
如果正则表达式包含多个空格,则使用 :s 比为每个空格使用 \s 更好。

11.8. 示例

让我们检查一个电子邮件地址是否有效。
在本例中,我们假设一个有效的电子邮件地址具有以下格式
名字 [点] 姓氏 [at] 公司 [点] (com/org/net)

本例中用于电子邮件验证的正则表达式不是很准确。
其唯一目的是演示 Raku 中的正则表达式功能。
不要在生产环境中按原样使用它。
脚本
my $email = '[email protected]';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;

if $email ~~ $regex {
  say $/ ~ " is a valid email";
} else {
  say "This is not a valid email";
}
输出

[email protected] 是一个有效的电子邮件地址

说明

<:L> 匹配单个字母
<:L>` 匹配一个或多个字母 + `\.` 匹配单个 [点] 字符 + `\@` 匹配单个 [at] 字符 + `<:L:N> 匹配一个字母或一个数字
<:L+:N>+ 匹配一个或多个字母或数字

正则表达式可以分解如下

  • 名字 <:L>+

  • [点] \.

  • 姓氏 <:L>+

  • [at] \@

  • 公司名称 <:L+:N>+

  • [点] \.

  • com/org/net <:L>+

或者,可以将一个正则表达式分解为多个命名的正则表达式
my $email = '[email protected]';
my regex many-letters { <:L>+ };
my regex dot { \. };
my regex at { \@ };
my regex many-letters-numbers { <:L+:N>+ };

if $email ~~ / <many-letters> <dot> <many-letters> <at> <many-letters-numbers> <dot> <many-letters> / {
  say $/ ~ " is a valid email";
} else {
  say "This is not a valid email";
}

命名的正则表达式使用以下语法定义:my regex 正则表达式名称 { 正则表达式定义 }
可以使用以下语法调用命名的正则表达式:<正则表达式名称>

有关正则表达式的更多信息,请参阅 https://raku-docs.perl5.cn/language/regexes

12. Raku 模块

Raku 是一种通用编程语言。它可以用来处理多种任务,包括:文本操作、图形、Web、数据库、网络协议等。

可重用性是一个非常重要的概念,程序员不必每次想做新任务时都重新发明轮子。

Raku 允许创建和重新分发**模块**。每个模块都是一组打包的功能,安装后即可重用。

**Zef** 是 Rakudo Star 附带的模块管理工具。

要安装特定模块,请在终端中键入以下命令

zef install "模块名称"

可以在以下位置找到 Raku 模块目录:https://raku.land/

12.1. 使用模块

MD5 是一种加密哈希函数,可生成 128 位的哈希值。
MD5 有多种应用,包括对数据库中存储的密码进行加密。当新用户注册时,他们的凭据不会以纯文本形式存储,而是经过“哈希”处理。其背后的原理是,如果数据库遭到入侵,攻击者将无法知道密码是什么。

幸运的是,您无需自己实现 MD5 算法;已经有现成的 Raku 模块可供使用。

让我们来安装它
zef install Digest::MD5

现在,运行以下脚本

use Digest::MD5;

my $password = "password123";
my $hashed-password = md5( $password );

say $hashed-password;

为了运行创建哈希的 md5() 函数,我们需要加载所需的模块。
use 关键字加载要在脚本中使用的模块,该模块提供了一个 md5 子例程。

在实践中,仅使用 MD5 哈希是不够的,因为它容易受到字典攻击。
它应该与盐值结合使用 https://en.wikipedia.org/wiki/Salt_(cryptography)

13. Unicode

Unicode 是一种字符编码标准,用于表示世界上大多数书写系统的文本。
UTF-8 是一种字符编码,能够对 Unicode 中所有可能的字符(或代码点)进行编码。

字符由以下内容定义
字形:视觉表示。
代码点:分配给字符的数字。
代码点名称:分配给字符的名称。

13.1. 使用 Unicode

让我们看看如何使用 Unicode 输出字符
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";

以上三行展示了构建字符的不同方法

  1. 直接写入字符(字形)

  2. 使用 \x 和代码点

  3. 使用 \c 和代码点名称

现在让我们输出一个笑脸
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";
另一个组合两个代码点的例子
say "á";
say "\x00e1";
say "\x0061\x0301";
say "\c[LATIN SMALL LETTER A WITH ACUTE]";

字母 á 可以写成

  • 使用其唯一的代码点 \x00e1

  • 或作为代码点 a 和尖音符 \x0061\x0301 的组合

可以使用的一些方法
say "á".NFC;
say "á".NFD;
say "á".uniname;
输出
NFC:0x<00e1>
NFD:0x<0061 0301>
LATIN SMALL LETTER A WITH ACUTE

NFC 返回唯一的代码点。
NFD 分解字符并返回每个部分的代码点。
uniname 返回代码点名称。

Unicode 字符可以用作标识符
my  = 1;
++;
say ;
Unicode 可以用于进行数学运算
my $var = 2 + ;
say $var;

13.2. Unicode 感知操作

13.2.1. 数字

阿拉伯数字是十个数字:0、1、2、3、4、5、6、7、8、9。这套数字是全世界使用最广泛的。

尽管如此,在世界不同地区,不同类型的数字也有一定程度的使用。

使用阿拉伯数字以外的数字集时,无需特别注意。所有方法/运算符都按预期工作。

say (٤,٥,٦,1,2,3).sort; # (1 2 3 4 5 6)
say 1 + ٩;              # 10

13.2.2. 字符串

如果我们使用通用的字符串操作,我们可能不会总是得到我们想要的结果,尤其是在比较或排序时。

比较
say 'a' cmp 'B'; # More

上面的例子表明 aB 大。原因是小写字母 a 的代码点大于大写字母 B 的代码点。

虽然从技术上讲这是正确的,但这可能不是我们想要的。

幸运的是,Raku 有一些方法/运算符可以实现 Unicode 排序算法
其中之一是 unicmp,它的行为类似于上面展示的 cmp,但它支持 Unicode。

say 'a' unicmp 'B'; # Less

如您所见,使用 unicmp 运算符现在可以得到预期的结果,即 a 小于 B

排序

作为使用代码点进行排序的 sort 方法的替代方法,Raku 提供了一个 collate 方法,该方法实现了 Unicode 排序算法

say ('a','b','c','D','E','F').sort;    # (D E F a b c)
say ('a','b','c','D','E','F').collate; # (a b c D E F)

14. 并行、并发和异步

14.1. 并行

在正常情况下,程序中的所有任务都是按顺序运行的。
这可能不是问题,除非您尝试做的事情需要很长时间。

幸运的是,Raku 具有使您能够并行运行的功能。
在这个阶段,重要的是要注意并行性可以指两种情况之一

  • 任务并行性:两个(或多个)独立表达式并行运行。

  • 数据并行性:单个表达式并行迭代元素列表。

让我们从后者开始。

14.1.1. 数据并行性

my @array = 0..50000;                       # Array population
my @result = @array.map({ is-prime $_ });   # call is-prime for each array element
say now - INIT now;                         # Output the time it took for the script to complete
考虑到上面的例子

我们只执行了一个操作 @array.map({ is-prime $_ })
is-prime 子例程正在被顺序调用,用于处理数组中的每个元素
即,先执行 is-prime @array[0],然后是 is-prime @array[1],接着是 is-prime @array[2],以此类推。

幸运的是,我们可以同时对多个数组元素调用 is-prime
my @array = 0..50000;                           # Array population
my @result = @array.race.map({ is-prime $_ });  # call is-prime for each array element
say now - INIT now;                             # Output the time it took to complete

请注意表达式中 race 的使用。 此方法将启用数组元素的并行迭代。

运行这两个示例(使用和不使用 race)后,比较两个脚本完成所需的时间。

race 不会保留元素的顺序。 如果你希望这样做,请改用 hyper

race
my @array = 1..1000;
my @result = @array.race.map( {$_ + 1} );
.say for @result;
hyper
my @array = 1..1000;
my @result = @array.hyper.map( {$_ + 1} );
.say for @result;

如果你运行这两个示例,你应该会注意到一个已排序,而另一个未排序。

14.1.2. 任务并行

my @array1 = 0..49999;
my @array2 = 2..50001;

my @result1 = @array1.map( {is-prime($_ + 1)} );
my @result2 = @array2.map( {is-prime($_ - 1)} );

say @result1 eqv @result2;

say now - INIT now;
考虑到上面的例子
  1. 我们定义了 2 个数组

  2. 对每个数组应用不同的操作并存储结果

  3. 并检查两个结果是否相同

脚本等待 @array1.map( {is-prime($_ + 1)} ) 完成
然后评估 @array2.map( {is-prime($_ - 1)} )

应用于每个数组的两个操作彼此不依赖。

为什么不并行执行这两个操作?
my @array1 = 0..49999;
my @array2 = 2..50001;

my $promise1 = start @array1.map( {is-prime($_ + 1)} ).eager;
my $promise2 = start @array2.map( {is-prime($_ - 1)} ).eager;

my @result1 = await $promise1;
my @result2 = await $promise2;

say @result1 eqv @result2;

say now - INIT now;
说明

start 子例程评估代码并返回一个 promise 类型的对象,或简称为promise
如果代码评估正确,则promise 将被保留
如果代码抛出异常,则promise 将被破坏

await 子例程等待promise
如果它被保留,它将获得返回值。
如果它被破坏,它将获得抛出的异常。

检查每个脚本完成所需的时间。

并行性始终会增加线程开销。 如果计算速度的提高没有抵消这种开销,则脚本看起来会更慢。
这就是为什么,对相当简单的脚本使用 racehyperstartawait 实际上会降低它们的速度。

14.2. 并发和异步

有关并发和异步编程的更多信息,请参阅 https://raku-docs.perl5.cn/language/concurrency

15. 本地调用接口

Raku 使我们能够使用 C 库,使用本机调用接口。

NativeCall 是 Raku 附带的标准模块,它提供了一组功能来简化 Raku 和 C 交互的工作。

15.1. 调用函数

考虑以下定义了一个名为 hellofromc 的函数的 C 代码。 此函数在终端上打印 Hello from C。 它不接受任何参数也不返回任何值。

ncitest.c
#include <stdio.h>

void hellofromc () {
  printf("Hello from C\n");
}

根据你的操作系统,运行以下命令将上述 C 代码编译成库。

在 Linux 上
gcc -c -fpic ncitest.c
gcc -shared -o libncitest.so ncitest.o
在 Windows 上
gcc -c ncitest.c
gcc -shared -o ncitest.dll ncitest.o
在 macOS 上
gcc -dynamiclib -o libncitest.dylib ncitest.c

在编译 C 库的同一目录中,创建一个包含以下代码的新 Raku 文件并运行它。

ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hellofromc() is native(LIBPATH) { * }

hellofromc();
说明

首先,我们声明我们将使用 NativeCall 模块。
然后我们创建了一个常量 LIBPATH,它保存 C 库的路径。
请注意,$*CWD 返回当前工作目录。
然后我们创建了一个名为 hellofromc() 的新 Raku 子例程,它应该充当其对应 C 函数的包装器,该函数具有相同的名称并驻留在 LIBPATH 中的 C 库中。
所有这些都是通过使用 is native 特性完成的。
最后我们调用了我们的 Raku 子例程。

本质上,这一切都归结为使用特性 is native 和 C 库的名称声明一个子例程。

15.2. 重命名函数

在上一部分中,我们看到了如何通过使用 is native 特性将其包装在具有相同名称的 Raku 子例程中来调用一个非常简单的 C 函数。

在某些情况下,我们想更改 Raku 子例程的名称。
为此,我们使用 is symbol 特性。

让我们修改上面的 Raku 脚本并将 Raku 子例程重命名为 hello 而不是 hellofromc

ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hello() is native(LIBPATH) is symbol('hellofromc') { * }

hello();
说明

如果 Raku 子例程的名称与其 C 对应名称不同,我们应该使用带有原始 C 函数名称的 is symbol 特性。

15.3. 传递参数

编译以下修改后的 C 库并再次运行以下 Raku 脚本。
请注意我们如何修改 C 和 Raku 代码以接受字符串(C 中的 char* 和 Raku 中的 Str

ncitest.c
#include <stdio.h>

void hellofromc (char* name) {
  printf("Hello, %s! This is C!\n", name);
}
ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hello(Str) is native(LIBPATH) is symbol('hellofromc') { * }

hello('Jane');

15.4. 返回值

让我们再重复一次该过程,并创建一个简单的计算器,该计算器接受 2 个整数并将它们相加。
编译 C 库并运行 Raku 脚本。

ncitest.c
int add (int a, int b) {
  return (a + b);
}
ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub add(int32,int32 --> int32) is native(LIBPATH) { * }

say add(2,3);

请注意 C 和 Raku 函数如何接受两个整数并返回一个(C 中的 int 和 Raku 中的 int32

15.5. 类型

你可能问过自己,为什么我们在最新的 Raku 脚本中使用 int32 而不是 Int
某些 Raku 类型(如 IntRat 等)不能按原样用于传递和接收来自 C 函数的值。
必须在 Raku 中使用与 C 中相同的类型。

幸运的是,Raku 提供了许多映射到其各自 C 对应类型的类型。

C 类型 Raku 类型

char

int8

int8_t

短整型

16位整数

int16_t

整数

32位整数

int32_t

int64_t

64位整数

无符号字符

8位无符号整数

uint8_t

无符号短整型

16位无符号整数

uint16_t

无符号整数

32位无符号整数

uint32_t

uint64_t

64位无符号整数

长整型

长整型

长长整型

长长整型

单精度浮点数

32位数值

双精度浮点数

64位数值

size_t

size_t

布尔值

布尔值

char* (字符串)

Str

数组:例如 int* (整数数组) 和 double* (双精度浮点数数组)

CArray:例如 CArray[int32]CArray[num64]

有关原生调用接口的更多信息,请参阅 https://raku-docs.perl5.cn/language/nativecall

16. 社区


1. 区间的表示法:https://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals