本页面旨在为熟悉 Node.js 的用户提供学习 Raku 的途径。我们将解释两种语言的共同特性,以及语法和功能上的主要差异。
这不是学习 Raku 的教程;这是针对已经具备 Node.js 中级到高级技能的用户提供的参考。
基本语法§
"Hello, world!"§
让我们从学习新语言时通常编写的第一个程序开始。在 Node.js 中,一个 hello world 程序将这样编写
console.log('Hello, world!');
以下是在 Raku 中以相同方式编写此程序的几种方法
say('Hello, world!');say 'Hello, world!';
在 Raku 中,函数调用中的括号是可选的。虽然在 Node.js 中分号在大多数情况下是可选的,但在 Raku 中,它们对于表达式是强制性的。
现在我们已经向世界问好,让我们向我们的好朋友 Joe 问好。我们再次从 Node.js 开始
let name = 'Joe';
console.log('What\'s up,' + name + '?');
console.log(`What's up, ${name}?`);
console.log("What's up, ", name, "?");
由于他没有听到我们的问候,让我们再次向他问好,这次用 Raku
my = 'Joe';say 'What\'s up, ' ~ ~ '?';say "What's up, $name?";say "What's up, ", , "?";
这里只有几个区别:Raku 中的大多数变量都有所谓的 sigils,即其名称前面的 $
,字符串连接使用 ~
运算符而不是 +
。这两种语言的共同点是支持字符串插值。
现在基本示例已经介绍完毕,让我们更详细地解释这两种语言之间的相似之处。
变量§
Node.js 中的变量可以这样定义;
var foo = 1; // Lexically scoped with functions and modules
let foo = 1; // Lexically scoped with blocks
const foo = 1; // Lexically scoped with blocks; constant
// No equivalent to Raku dynamic variables exists.
global.foo = 1; // Globally scoped
foo = 1; // Ditto, but implicit; forbidden in strict mode
在 Raku 中,没有等效于 var
的东西。需要注意的是,Raku 中没有变量提升;变量在它们所在的代码行上定义和赋值,而不是在作用域顶部定义,然后在该行上赋值。
除了普通变量之外,Raku 中还有所谓的动态变量。动态变量使用调用者的作用域进行查找,而不是外部作用域。以下是 Raku 中等效的变量声明
my = 1; # Lexically scopedour = 1; # Package scoped
my constant foo = 1; # Lexically scoped; constant
constant foo = 1; # Package scoped; constant
my = 1; # Dynamic variable; lexically scopedour = 1; # Dynamic variable; package scoped
GLOBAL::<$foo> := 1; # Globally scoped
在需要使用 let
的地方使用 my
,在需要在最外层作用域中定义变量的地方使用 our
,在需要使用 const
的地方使用 constant
。
您可能已经注意到变量名前面的 $
和 $*
符号。这些分别被称为 sigils 和 twigils,它们定义了变量所属的容器。有关 sigils、twigils 和容器的更多信息,请参阅有关 变量 的文档。
Node.js 中的变量可以与来自外部作用域的其他变量具有相同的名称,而不会发生冲突(尽管 linter 通常会根据其配置方式对此进行抱怨)
let foo = 1;
function logDupe() {
let foo = 2;
console.log(foo);
}
logDupe(2); // OUTPUT: 2
console.log(foo); // OUTPUT: 1
Raku 也允许这样做
my = 1;sub log-dupelog-dupe; # OUTPUT: 2say ; # OUTPUT: 1
运算符§
赋值§
=
运算符在两种语言中都具有相同的作用。
Raku 中的 :=
运算符将值绑定到变量。将变量绑定到另一个变量会使它们具有相同的值和容器,这意味着对其中一个的属性进行修改也会修改另一个的属性。绑定变量不能使用 =
重新赋值,也不能使用 ++
、--
等进行修改,但可以再次绑定到另一个值
my ; # This is a hash, roughly equivalent to a JS object or mapmy = ;my := ;<foo> = 'bar';say ; # OUTPUT: {}say ; # OUTPUT: {foo => bar}:= ;say ; # OUTPUT: {}
相等性§
Node.js 有两个相等运算符:==
和 ===
。
==
是松散相等运算符。当比较类型相同的操作数时,如果两个操作数相等,它将返回 true。但是,如果操作数的类型不同,则它们会在比较之前被强制转换为其基本类型,这意味着这些将返回 true
console.log(1 == 1); // OUTPUT: true
console.log('1' == 1); // OUTPUT: true
console.log([] == 0); // OUTPUT: true
类似地,在 Raku 中,如果操作数的类型不同,则它们都会在比较之前被强制转换为 Numeric
say 1 == 1; # OUTPUT: Truesay '1' == 1; # OUTPUT: Truesay [1,2,3] == 3; # OUTPUT: True, since the array has three elements
==
的反义词是 !=
。
Raku 还有另一个类似于 ==
的运算符:eq
。它不会将操作数强制转换为 Numeric(如果它们类型不同),而是将它们强制转换为字符串
say '1' eq '1'; # OUTPUT: Truesay 1 eq '1'; # OUTPUT: True
eq
的反义词是 ne
或 !eq
。
===
是严格相等运算符。如果两个操作数的值相同,则它将返回 true。当比较对象时,这将 *仅* 在它们是完全相同的对象时返回 true
console.log(1 === 1); // OUTPUT: true
console.log('1' === 1); // OUTPUT: false
console.log({} === {}); // OUTPUT: false
let obj = {};
let obj2 = obj;
console.log(obj === obj2); // OUTPUT: true;
在 Raku 中,运算符的行为相同,但有一个例外:两个具有相同值但容器不同的对象将返回 false
say 1 === 1; # OUTPUT: «True»say '1' === 1; # OUTPUT: «False»say 'ayy lmao' === 'ayy lmao'; # OUTPUT: «True»say === ; # OUTPUT: «False»my \hash = ;my = hash;say hash === ; # OUTPUT: False
在最后一种情况下,对象相同,但容器不同,这就是它返回 False 的原因。
===
的反义词是 !==
。
这就是 Raku 的其他相等运算符发挥作用的地方。如果值具有不同的容器,则可以使用 eqv
运算符。此运算符也可用于检查深度相等性,通常需要在 Node.js 中使用库来实现。
say eqv ; # OUTPUT: Truemy \hash = ;my := hash;say hash eqv ; # OUTPUT: True
如果需要检查两个变量是否具有相同的容器和值,请使用 =:=
运算符。
my = [1,2,3];my := ; # Bound variables keep the container of the other variablesay =:= ; # OUTPUT: True
智能匹配§
Raku 还有一个用于比较值的运算符,但它不完全是相等运算符。这就是 ~~
,智能匹配运算符。它有几种用途:它可以像 Node.js 中的 instanceof
一样使用,匹配正则表达式,以及检查值是否为哈希、包、集合或映射中的键。
say 'ayy lmao' ~~ Str; # OUTPUT: Truemy = a => 1;say 'a' ~~ ; # OUTPUT: Truemy = 'abc';~~ s/abc/def/; # Mutates $str, like foo.replace('abc', 'def')say ; # OUTPUT: def
说到 instanceof
,Raku 中等效于 Node.js 对象上的 constructor
属性的是 WHAT
属性。
console.log('foo'.constructor); // OUTPUT: String
say 'foo'.WHAT; # OUTPUT: Str
数字§
Node.js 有 +
、-
、/
、*
、%
和(在 ES6 中)**
作为数字运算符。当操作数类型不同时,与相等运算符类似,它们会在进行操作之前转换为其基本类型,从而使这成为可能。
console.log(1 + 2); // OUTPUT: 3
console.log([] + {}); // OUTPUT: [object Object]
console.log({} + []); // OUTPUT: 0
在 Raku 中,它们再次被转换为数字类型,如前所述。
say 1 + 2; # OUTPUT: 3say [] + ; # OUTPUT: 0say + [1,2,3]; # OUTPUT: 3
此外,Raku 还有 div
和 %%
。div
的行为类似于 C 中的 int
除法,而 %%
检查一个数字是否可以被另一个数字整除。
say 4 div 3; # OUTPUT: 1say 4 %% 3; # OUTPUT: Falsesay 6 %% 3; # OUTPUT: True
按位§
Node.js 有 &
、|
、^
、~
、<<
、>>
、>>>
和 ~
作为按位运算符。
console.log(1 << 1); // OUTPUT: 2
console.log(1 >> 1); // OUTPUT: 0
console.log(1 >>> 1); // OUTPUT: 0
console.log(1 & 1); // OUTPUT: 1
console.log(0 | 1); // OUTPUT: 1
console.log(1 ^ 1); // OUTPUT: 0
console.log(~1); // OUTPUT: -2
在 Raku 中,没有等效于 >>>
的运算符。所有按位运算符都以 +
为前缀,但按位取反使用 +^
而不是 ~
。
say 1 +< 1; # OUTPUT: 2say 1 +> 1; # OUTPUT: 0# No equivalent for >>>say 1 +& 1; # OUTPUT: 1say 0 +| 1; # OUTPUT: 1say 1 +^ 1; # OUTPUT: 0say +^1; # OUTPUT: -2
检查定义§
Javascript 包含一个空值合并运算符 ??
,它仅在为空或未定义时才会进行。
undefined || null || 0 || 1 ; // => 1
undefined ?? null ?? 0 ?? 1 ; // => 0
这与 Raku //
运算符非常相似。
Any || Nil || 0 || 1 ; # => 1Any // Nil // 0 // 1 ; # => 0
自定义运算符和运算符重载§
Node.js 不允许运算符重载,除非使用 Makefile 或使用自定义版本的 V8 构建 Node.js。Raku 本地允许自定义运算符和运算符重载!由于所有运算符都是子例程,因此您可以像这样定义自己的运算符。
# "distance operator": the distance of two numbers is the absolute value# of their differencemulti infix:<|-|>(, ) is equiv(:<->)say -1 |-| 3; # OUTPUT: 4
运算符可以定义为 prefix
、infix
或 postfix
。is tighter
、is equiv
和 is looser
特性可选地定义运算符的优先级。在本例中,|-|
与 -
具有相同的优先级。
请注意,在声明运算符子例程时如何使用 multi
。这允许声明具有相同名称但具有不同签名的多个子例程。这将在 函数 部分中详细解释。现在,我们只需要知道它允许我们覆盖我们想要的任何原生运算符。
# Using the `is default` trait here forces this subroutine to be chosen first,# so long as the signature of the subroutine matches.multi prefix:<++>() is defaultmy = 1;say ++; # OUTPUT: 0
控制流§
if/else§
您应该熟悉 JavaScript 中 if
/else
的外观。
let diceRoll = Math.ceil(Math.random() * 6) + Math.ceil(Math.random() * 6);
if (diceRoll === 2) {
console.log('Snake eyes!');
} else if (diceRoll === 16) {
console.log('Boxcars!');
} else {
console.log(`Rolled ${diceRoll}.`);
}
在 Raku 中,if
/else
的工作方式基本相同,但有一些关键区别。第一,不需要括号。第二,else if
写成 elsif
。第三,if 子句可以写在语句之后。
my Int = ceiling rand * 12 + ceiling rand * 12;if == 2elsif == 16else
或者,虽然效率较低,但这可以写成在语句之后使用 if
。
my Int = ceiling rand * 12 + ceiling rand * 12;say 'Snake eyes!' if == 2;say 'Boxcars!' if == 16;say "Rolled $dice-roll." if != 2 && != 16;
Raku 还有 when
,它类似于 if
,但如果给定的条件为真,则不会执行其所在块中 when
块之后的任何代码。
此外,Raku 还有 with
、orwith
和 without
,它们分别类似于 if
、else if
和 else
,但它们不是检查其条件是否为真,而是检查其是否已定义。
switch§
Switch 语句是一种检查给定值与一组值之间是否相等的方法,如果匹配,则运行一些代码。case
语句定义要比较的每个值。default
(如果包含)充当给定值与任何情况都不匹配时的后备。匹配一个情况后,通常使用break
来防止执行匹配情况之后的情况的代码,尽管很少有意省略它。
const ranklist = [2, 3, 4, 5, 6, 7, 8, 9, 'Jack', 'Queen', 'King', 'Ace'];
const ranks = Array.from(Array(3), () => ranklist[Math.floor(Math.random() * ranks.length)]);
let score = 0;
for (let rank of ranks) {
switch (rank) {
case 'Jack':
case 'Queen':
case 'King':
score += 10;
break;
case 'Ace';
score += (score <= 11) ? 10 : 1;
break;
default:
score += rank;
break;
}
}
在 Raku 中,given
可以像 switch 语句一样使用。没有等效于 break
的内容,因为 when
块最常像 case
语句一样使用。switch
和 given
之间的一个主要区别是,传递给 switch
语句的值只会匹配与该值完全相等的 case;given
值与 when
值进行智能匹配 (~~
)。
my = [2, 3, 4, 5, 6, 7, 8, 9, 'Jack', 'Queen', 'King', 'Ace'];my = .pick: 3;my Int = 0;for ->
如果有多个 when
块与传递给 given
的值匹配,并且您希望运行多个块,请使用 proceed
。succeed
可用于退出其所在的 when
块和 given 块,防止执行任何后续语句。
given Int# OUTPUT:# Int is Int# Int is Numeric# Int is Any
for、while 和 do/while§
JavaScript 中有三种不同类型的 for 循环。
// C-style for loops
const letters = {};
for (let ord = 0x61; ord <= 0x7A; ord++) {
let letter = String.fromCharCode(ord);
letters[letter] = letter.toUpperCase();
}
// for..in loops (typically used on objects)
for (let letter in letters) {
console.log(letters[letter]);
}
# OUTPUT:
# A
# B
# C
# etc.
// for..of loops (typically used on arrays, maps, and sets)
for (let letter of Object.values(letters)) {
console.log(letter);
}
# OUTPUT:
# A
# B
# C
# etc.
Raku for
循环最类似于 for..of
循环,因为它们适用于任何可迭代的东西。可以使用 loop
编写 C 样式循环,但这不建议这样做,因为它们最好写成使用范围的 for
循环。与 if
语句一样,for
可以跟随一个语句,当前迭代可以使用 $_
变量(称为“it”)访问。$_
上的方法可以在不指定变量的情况下调用。
my Str ;= .uc for 'a'..'z';.say for .values;# OUTPUT:# A# B# C# etc.
while
循环在 JavaScript 和 Raku 之间的工作方式相同。Raku 还有 until
循环,它们不是迭代到给定条件为假,而是迭代到条件为真。
do/while
循环在 Raku 中被称为 repeat/while
循环。与 while
一样,repeat/until
循环也存在,并在给定条件为假之前循环。
要在 Raku 中编写无限循环,请使用 loop
而不是 for
或 while
。
在 JavaScript 中,continue
用于跳过循环中的下一个迭代,break
用于提前退出循环。
let primes = new Set();
let i = 2;
do {
let isPrime = true;
for (let prime of primes) {
if (i % prime == 0) {
isPrime = false;
break;
}
}
if (!isPrime) continue;
primes.add(i);
} while (++i < 20);
console.log(primes); # OUTPUT: Set { 2, 3, 5, 7, 11, 13, 17, 19 }
在 Raku 中,它们分别被称为 next
和 last
。还有 redo
,它重复当前迭代,而不再次评估循环的条件。
next
/redo
/last
语句后面可以跟着在外部循环之前定义的标签,以使语句作用于标签所指的循环,而不是语句所在的循环。
my is SetHash;my Int = 2;OUTSIDE:repeatwhile ++ < 20;say ; # OUTPUT: SetHash(11 13 17 19 2 3 5 7)
do§
do
目前不是 JavaScript 中的功能,但已经提出了一项提案来将其添加到 ECMAScript。do
表达式评估一个块并返回结果。
constant VERSION = v2.0.0;constant VERSION_NUMBER = do;say VERSION_NUMBER; # OUTPUT: 33554432
类型§
创建类型§
在 JavaScript 中,类型是通过创建类(或 ES5 及更早版本中的构造函数)来创建的。如果您使用过 TypeScript,您可以将类型定义为其他类型的子集,如下所示
type ID = string | number;
在 Raku 中,类、角色、子集和枚举被认为是类型。创建类和角色将在本文的 OOP 部分中讨论。创建 ID 子集可以像这样完成
where Str | Int;
有关更多信息,请参阅有关subset 和Junction
的文档。
TypeScript 枚举可以具有数字或字符串作为其值。定义值是可选的;默认情况下,第一个键的值为 0,下一个键的值为 1,下一个键的值为 2,依此类推。例如,以下是一个枚举,它为扩展 ASCII 箭头符号定义方向(可能用于 TUI 游戏)
enum Direction (
UP = '↑',
DOWN = '↓',
LEFT = '←',
RIGHT = '→'
);
Raku 中的枚举可以具有任何类型作为其键的值。枚举键(以及可选的值)可以通过编写 enum
、枚举的名称,然后是键的列表(以及可选的值)来定义,这可以使用< >、« » 或( ) 来完成。如果您想为枚举的键定义值,则必须使用 ( )
。以下是 Raku 中编写的 Direction 枚举
(UP => '↑',DOWN => '↓',LEFT => '←',RIGHT => '→');
有关更多信息,请参阅有关enum 的文档。
使用类型§
在 TypeScript 中,您可以定义变量的类型。尝试分配与变量类型不匹配的值将导致转译器出错。这是这样完成的
enum Name (Phoebe, Daniel, Joe);
let name: string = 'Phoebe';
name = Phoebe; # Causes tsc to error out
let hobbies: [string] = ['origami', 'playing instruments', 'programming'];
let todo: Map<string, boolean> = new Map([
['clean the bathroom', false],
['walk the dog', true],
['wash the dishes', true]
]);
let doJob: (job: string) => boolean = function (job: string): boolean {
todo.set(job, true);
return true;
};
在 Raku 中,变量可以通过将类型放在声明符 (my
、our
等) 和变量名称之间来进行类型化。分配与变量类型不匹配的值将抛出编译时或运行时错误,具体取决于值的评估方式。
<Phoebe Daniel Joe>;my Str = 'Phoebe';= Phoebe; # Throws a compile-time error# The type here defines the type of the elements of the array.my Str = ['origami', 'playing instruments', 'programming'];# The type between the declarator and variable defines the type of the values# of the hash.# The type in the curly braces defines the type of the keys of the hash.my Bool = ('clean the bathroom' => False,'walk the dog' => True,'wash the dishes' => True);# The type here defines the return value of the routine.my Bool = sub (Str --> Bool);
比较 JavaScript 和 Raku 类型§
以下是 JavaScript 类型及其在 Raku 中的等效类型的表格
JavaScript | Raku |
---|---|
Object | Mu、Any、Hash |
Array | List、Array、Seq |
String | Str |
Number | Int、Num、Rat |
Boolean | Bool |
Map | Map、Hash |
Set | Set、SetHash |
Object
既是 JavaScript 中所有类型的超类,也是创建哈希的一种方式。在 Raku 中,Mu
是所有类型的超类,尽管通常您希望使用 Any
,它是 Mu
的子类,但也是几乎所有类型的超类,Junction
是一个例外。当使用 Object
作为哈希时,Hash
是您想要使用的。Object
和 Hash
之间的一个关键区别是 Object
保留其键的顺序;Hash
默认情况下不保留。
有三种类型等效于 Array
。 Array
最类似于 Array
,因为它充当可变数组。 List
类似于 Array
,但它是不可变的。 Seq
用于创建延迟数组。
String
和 Str
在大多数情况下使用方式相同。
Raku 中有几种不同的类型等效于 Number
,但您最常看到的三个是 Int
、Num
和 Rat
。 Int
表示整数。 Num
表示浮点数,使其最类似于 Number
。 Rat
表示两个数字的分数,当 Num
无法提供足够精确的值时使用。
Boolean
和 Bool
在大多数情况下使用方式相同。
Map
在 Raku 中既有可变的也有不可变的等效类型。 Map
是不可变的,而 Hash
是可变的。不要混淆它们!与 JavaScript 中的 Map
一样,Map
和 Hash
可以具有任何类型的键或值,而不仅仅是键的字符串。
与 Map
一样,Set
在 Raku 中也有可变的和不可变的等效类型。 Set
是不可变的,而 SetHash
是可变的。
函数§
# 待定
面向对象编程§
# 待定
异步编程§
# 待定
缓冲区§
NodeJS 使用类 Buffer
和 Blob
处理原始二进制数据,而 Raku 使用角色 Buf
和 Blob
,它们分别是可变和不可变的缓冲区。在 Raku 中,`Buf` 组成一个 `Blob`,因此所有 `Blob` 方法都可用于 `Buf` 对象。
下表总结了 NodeJS 和 Raku 中缓冲区构造之间的异同
类/角色 | NodeJS | Raku |
---|---|---|
`Buffer`/`Buf` | 固定长度的字节序列(没有 `push`、`pop` 等方法) | 可以动态增长或缩小的字节序列。您可以使用 `push`、`pop` 等方法。 |
使用 `for..of` 语法可迭代 | 可以使用循环结构进行迭代。 | |
可以使用数组索引更新每个字节,例如 `buf[i]++`。 | 与 NodeJS 相同。 | |
`Blob` | 固定长度的字节序列(没有 `push`、`pop` 等方法) | 与 NodeJS 相同。 |
不可迭代。 | 可以使用循环结构进行迭代。 | |
每个字节都是不可变的。 | 与 NodeJS 相同。 |
创建缓冲区§
在 NodeJS 中,有几种方法可以创建新的缓冲区。您可以使用静态方法 Buffer.alloc
为 n
个字节的缓冲区分配内存,除非提供了 `fill` 参数。
const zeroBuf = Buffer.alloc(8);
const charBuf = Buffer.alloc(8, 97, 'utf-8');
console.log(zeroBuf); // OUTPUT: «<Buffer 00 00 00 00 00 00 00 00>»
console.log(charBuf); // OUTPUT: «<Buffer 61 61 61 61 61 61 61 61>»
在 Raku 中,您可以使用 allocate
方法
my = Blob.allocate(8);my = Blob.allocate(8, 97);say ; # OUTPUT: «Blob:0x<00 00 00 00 00 00 00 00>»say ; # OUTPUT: «Blob:0x<61 61 61 61 61 61 61 61>»my = Buf.allocate(8);my = Buf.allocate(8, 97);say ; # OUTPUT: «Buf:0x<00 00 00 00 00 00 00 00>»say ; # OUTPUT: «Buf:0x<61 61 61 61 61 61 61 61>»
您还可以将缓冲区初始化为整数数组的内容
const buf = Buffer.from([ 114, 97, 107, 117 ]);
console.log(buf); // OUTPUT: «<Buffer 72 61 6b 75>»
在 Raku 中,您可以使用 new
构造函数来实现相同的功能。
my = Blob.new(114, 97, 107, 117);say ; # OUTPUT: «Blob:0x<72 61 6B 75>»my = Buf.new(114, 97, 107, 117);say ; # OUTPUT: «Buf:0x<72 61 6B 75>»
类似地,您可以使用 from
方法将缓冲区初始化为字符串的二进制编码。
const buf = Buffer.from('NodeJS & Raku', 'utf-8');
console.log(buf); // OUTPUT: «<Buffer 4e 6f 64 65 4a 53 20 26 20 52 61 6b 75>»
在 Raku 中,您可以在字符串上调用 encode
方法,该方法返回一个 Blob
。
my = "NodeJS & Raku".encode('utf-8');say ; # OUTPUT: «utf8:0x<4E 6F 64 65 4A 53 20 26 20 52 61 6B 75>»
注意: 在 Raku 中,将字符的 Blob 传递给与缓冲区相关的函数时,必须显式地对字符进行编码。
要解码字符串的二进制编码,您可以在缓冲区上调用 toString
方法。
const buf = Buffer.from([ 114, 97, 107, 117 ]);
console.log(buf.toString('utf-8')); // OUTPUT: «raku»
在 Raku 中,您可以在缓冲区上调用 decode
方法。
my = Blob.new(114, 97, 107, 117);say .decode('utf-8'); # OUTPUT: «raku»
写入缓冲区§
在 NodeJS 中,您可以使用 write
方法写入缓冲区。
const buf = Buffer.alloc(16);
buf.write('Hello', 0, 'utf-8');
console.log(buf); // OUTPUT: «<Buffer 48 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00>»
buf.write(' world!', 5, 'utf-8');
console.log(buf); // OUTPUT: «<Buffer 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 00 00 00 00>»
在 Raku 中,没有 write
方法。但是,您可以使用 splice
方法用其他元素覆盖缓冲区的元素。
my = Buf.allocate(16);.splice(0, 5, 'Hello'.encode('utf-8'));say ; # OUTPUT: «Buf:0x<48 65 6C 6C 6F 00 00 00 00 00 00 00 00 00 00 00>».splice(5, 7, ' world!'.encode('utf-8'));say ; # OUTPUT: «Buf:0x<48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 00 00 00>»
从缓冲区读取§
有多种方法可以访问缓冲区中的数据,从访问单个字节到提取整个内容,再到解码其内容。
const buf = Buffer.from('Hello', 'utf-8');
console.log(buf[0]); // OUTPUT: «72»
在 Raku 中,您还可以使用 []
对缓冲区的字节进行索引。
my = 'Hello'.encode('utf-8');say [0]; # OUTPUT: «72»
在 NodeJS 中,从缓冲区检索所有数据的最常见方法是使用 toString
方法(假设缓冲区编码为文本)。
const buf = Buffer.from('Hello');
const buf = Buffer.alloc(16);
buf.write('Hello world', 0, 'utf-8');
console.log(buf.toString('utf-8')); // OUTPUT: «Hello world!\u0000t»
我们可以向 toString
提供偏移量和长度,以仅从缓冲区读取相关字节。
console.log(buf.toString('utf-8', 0, 12)); // OUTPUT: «Hello world!»
在 Raku 中,您可以使用 decode
方法执行相同的操作。
my = Buf.allocate(16);.splice(0, 12, 'Hello world'.encode('utf-8'));;say .decode('utf-8').raku; # OUTPUT: «Hello world!\0\0\0\0>»
但是,您不能同时对缓冲区进行切片和解码。相反,您可以使用 subbuf
从调用者缓冲区中提取相关部分,然后对返回的缓冲区进行解码。
say .subbuf(0, 12).decode('utf-8').raku; # OUTPUT: «Hello world!>»
更多有用的方法§
Buffer.isBuffer
§
在 NodeJS 中,您可以使用 isBuffer
方法检查对象是否为缓冲区。
const buf = Buffer.from('hello');
console.log(Buffer.isBuffer(buf)); // OUTPUT: «true»
在 Raku 中,您可以与 Blob
或 Buf
进行智能匹配(请记住,Buf
包含 Blob
)。
my = 'hello'.encode();my = Buf.allocate(4);say ~~ Blob; # OUTPUT: «True»say ~~ Buf; # OUTPUT: «False»say ~~ Buf; # OUTPUT: «True»say ~~ Blob; # OUTPUT: «True»
Buffer.byteLength
§
要检查对字符串进行编码所需的字节数,可以使用 Buffer.byteLength
。
const camelia = '🦋';
console.log(Buffer.byteLength(camelia)); // OUTPUT: «4»
在 Raku 中,您可以使用 bytes
方法。
my = '🦋';say .encode.bytes; # OUTPUT: «4»
注意: 字节数与字符串的长度不同。这是因为许多字符需要比其长度所显示的更多的字节进行编码。
length
§
在 NodeJS 中,您使用 length
方法来确定缓冲区分配了多少内存。这与缓冲区内容的大小不同。
const buf = Buffer.alloc(16);
buf.write('🦋');
console.log(buf.length); // OUTPUT: «16»
在 Raku 中,您可以使用 elems
方法。
my = Buf.allocate(16);.splice(0, '🦋'.encode.bytes, '🦋'.encode('utf-8'));say .elems; # OUTPUT: «16»
copy
§
您可以使用 copy
方法将一个缓冲区的内容复制到另一个缓冲区。
const target = Buffer.alloc(24);
const source = Buffer.from('🦋', 'utf-8');
target.write('Happy birthday! ', 'utf-8');
source.copy(target, 16);
console.log(target.toString('utf-8', 0, 20)); // OUTPUT: «Happy birthday! 🦋»
Raku 中没有 copy
方法,但是您可以使用 splice
方法获得相同的结果。
my = Buf.allocate(24);my = 'Happy birthday! '.encode('utf-8');.splice(0, .bytes, );my = '🦋'.encode('utf-8');.splice(16, .bytes, );say .subbuf(0, 20).decode('utf-8'); # OUTPUT: «Happy birthday! 🦋»
slice
§
您可以使用 slice
方法对缓冲区进行切片,该方法返回对内存空间子集的引用。因此,修改切片也会修改原始缓冲区。
// setup
const target = Buffer.alloc(24);
const source = Buffer.from('🦋', 'utf-8');
target.write('Happy birthday! ', 'utf-8');
source.copy(target, 16);
// slicing off buffer
const animal = target.slice(16, 20);
animal.write('🐪');
console.log(animal.toString('utf-8'); // OUTPUT: «🐪»
console.log(target.toString('utf-8', 0, 20)); // OUTPUT: «Happy birthday! 🐪»
在这里,我们切掉了 target
并将生成的缓冲区存储在 animal
中,我们最终对其进行了修改。这导致 target
被修改。
在 Raku 中,您可以使用 subbuf
方法。
# setupmy = Buf.allocate(24);my = 'Happy birthday! '.encode('utf-8');.splice(0, .bytes, );my = '🦋'.encode('utf-8');.splice(16, .bytes, );# slicing off buffermy = .subbuf(16, 20);.splice(0, .bytes, '🐪'.encode('utf-8'));say .decode; # OUTPUT: «🐪»say .subbuf(0, 20).decode('utf-8'); # OUTPUT: «Happy birthday! 🦋»
但是,与 NodeJS 的 slice
方法不同,subbuf
返回一个全新的缓冲区。要获取对缓冲区子集的可写引用,请使用 subbuf-rw
。
# setupmy = Buf.allocate(24);my = 'Happy birthday! '.encode('utf-8');.splice(0, .bytes, );my = '🦋'.encode('utf-8');.splice(16, .bytes, );# slicing off buffer.subbuf-rw(16, 4) = '🐪'.encode('utf-8');say .subbuf(0, 20).decode('utf-8'); # OUTPUT: «Happy birthday! 🐪»
网络 API§
Net§
在 Raku 中,有两个 API 用于处理网络:IO::Socket::INET
(用于同步网络)和 IO::Socket::Async
(用于异步网络)。
IO::Socket::INET
目前仅支持 TCP 连接。它的 API 类似于 C 的套接字 API。如果您熟悉该 API,那么您将很快理解如何使用它。例如,以下是一个回声服务器,它在收到第一个消息后关闭连接。
my IO::Socket::INET .= new::localhost<localhost>,:localport<8000>,:listen;my IO::Socket::INET .= new: :host<localhost>, :port<8000>;.print: 'Hello, world!';my IO::Socket::INET = .accept;my Str = .recv;say ; # OUTPUT: Hello, world!.print();say .recv; # OUTPUT: Hello, world!.close;.close;.close;
默认情况下,IO::Socket::INET
连接仅限于 IPv4。要使用 IPv6,请在构建服务器或客户端时传递 :family(PF_INET6)
。
相反,IO::Socket::Async
支持 IPv4 和 IPv6,无需指定要使用的族。它还支持 UDP 套接字。以下是如何异步编写与上面相同的回声服务器(请注意,Supply.tap
是多线程的;如果这是不可取的,请改用 Supply.act
)。
my = IO::Socket::Async.listen('localhost', 8000);my = .tap(->);my = await IO::Socket::Async.connect('localhost', 8000);.Supply.tap(->);await .print: 'Hello, world!';
在 Node.js 中,等效的代码如下所示
const net = require('net');
const server = net.createServer(conn => {
conn.setEncoding('utf8');
conn.on('data', data => {
console.log(data); # OUTPUT: Hello, world!
conn.write(data);
conn.end();
});
}).listen(8000, 'localhost');
const client = net.createConnection(8000, 'localhost', () => {
client.setEncoding('utf8');
client.on('data', data => {
console.log(data); # OUTPUT: Hello, world!
client.end();
server.close();
});
client.write("Hello, world!");
});
HTTP/HTTPS§
Raku 本身并不支持 HTTP/HTTPS。但是,CPAN 上的包,例如 Cro,可以帮助弥补这一差距。
DNS§
Raku 目前不支持 Node.js 的 DNS 模块实现的大部分功能。 IO::Socket::INET
和 IO::Socket::Async
可以解析主机名,但尚未实现解析 DNS 记录和反向 IP 查找等功能。有一些模块正在开发中,例如 Net::DNS::BIND::Manage,旨在改进 DNS 支持。
Punycode§
Punycode 支持可以通过 CPAN 上的 Net::LibIDN、Net::LibIDN2 和 IDNA::Punycode 模块获得。
文件系统 API§
# 待定
模块和包§
# 待定