Wait the light to fall

Javascript(Node.js) 到 Raku - 简而言之

焉知非鱼

大西瓜啊,忘记翻译了!

This page attempts to provide a way for users experienced in Node.js to learn Raku. Features shared between the two languages will be explained here, as well as major differences in syntax and features.

This is not a tutorial for learning Raku; this is a reference for users who are already at an intermediate to advanced skill level with Node.js.

此页面试图为在Node.js中有经验的用户提供学习Raku的方法。这里将解释两种语言之间共享的功能,以及语法和功能的主要差异。

这不是学习Raku的教程; 对于已经使用Node.js处于中级到高级技能级别的用户,这是一个参考。

基础语法

“Hello, world!” #

Let’s start with the typical first program when learning new languages. In Node.js, a hello world program would be written like this:

让我们从学习新语言时的典型第一个程序开始。在Node.js中,一个hello world程序将编写如下:

console.log('Hello, world!');

Here are a couple ways to write this in the same way in Raku:

以下是在Raku中以相同方式编写此内容的几种方法:

say('Hello, world!');
say 'Hello, world!';

Parentheses are optional for function calls in Raku. While semicolons are, for the most part, optional in Node.js, they are mandatory for expressions in Raku.

Now that we’ve greeted the world, let’s greet our good friend, Joe. We’ll start with Node.js again:

对于Raku中的函数调用,括号是可选的。虽然分号在Node.js中大多数是可选的,但它们对于Raku中的表达式是必需的。

现在我们迎接了世界,让我们迎接我们的好朋友乔。我们将再次从Node.js开始:

let name = 'Joe';
console.log('What\'s up,' + name + '?');
console.log(`What's up, {name}?`);
console.log("What's up, ", name, "?");

Since he didn’t hear us, let’s greet him again, this time in Raku:

因为他没有听到我们,所以让我再次问候他,这次是在Raku中:

my $name = 'Joe';
say 'What\'s up, ' ~ $name ~ '?';
say "What's up, $name?";
say "What's up, ", $name, "?";

Here, there are only a couple differences: most variables in Raku have what are called sigils, which are what the $ in front of its name is, and string concatenation uses the ~ operator instead of +. What the two languages share in common here is support for string interpolation.

Now that the basic examples are out of the way, let’s explain the similarities between the two languages in greater detail.

这里只有几个不同之处:Raku中的大多数变量都有所谓的sigils,这就是$它名称前面的变量,字符串连接使用~运算符代替+。这两种语言的共同点是支持字符串插值。

现在基本的例子已经不在了,让我们更详细地解释两种语言之间的相似之处。

变量 #

Variables in Node.js can be defined like this;

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
 
global.foo = 1; // Dynamically scoped; global
foo = 1;        // Ditto, but implicit; forbidden in strict mode

In Raku there is no equivalent to var. An important note to make is that there is no variable hoisting in Raku; variables are defined and assigned at the line they’re on, not defined at the top of its scope and later assigned at that line.

This is how the equivalent types of variables are defined in Raku:

在Raku中没有相当于var。需要注意的一点是,Raku中没有可变的吊装; 变量在它们所在的行上定义和分配,未在其范围的顶部定义,稍后在该行分配。

这是在Raku中定义等效类型的变量的方式:

my $foo = 1;      # Lexically scoped with blocks 
our $foo = 1;     # Lexically scoped with blocks and modules 
constant foo = 1; # Lexically scoped with blocks and modules; constant 
 
my $*foo = 1;       # Dynamically scoped with blocks 
OUR::<$foo> = 1;    # Dynamically scoped with blocks and modules 
GLOBAL::<$foo> = 1; # Dynamically scoped; global 

Use my where you’d use let, our for variables you’d define in the outermost scope needed, and constant where you’d uses const.

Dynamically scoped variables are not referred to in the same way as lexically scoped ones like they are in Node.js. User-defined ones use either a $*, @*, %*, or &* twigil. Refer to the documentation on variables for more information on sigils, twigils, and variable containers.

Variables in Node.js can override others from outer scopes with the same name (though linters will usually complain about it depending on how they’re configured):

使用my您使用的位置letour您需要在最外层范围内定义的变量以及constant您使用的位置const

动态范围变量的引用方式与它们在Node.js中的词汇范围变量相同。用户定义的那些使用一个$*@*%*,或&*twigil。有关sigils,twigils和变量容器的更多信息,请参阅有关变量的文档。

Node.js中的变量可以覆盖具有相同名称的外部作用域中的其他变量(尽管linters通常会根据它们的配置方式来抱怨它):

let foo = 1;
function logDupe() {
    let foo = 2;
    console.log(foo);
}
 
logDupe(2);       // 2
console.log(foo); // 1

Raku also allows this:

Raku 也允许这样:

my $foo = 1;
sub log-dupe {
    my $foo = 2;
    say $foo;
}
 
log-dupe; # 2 
say $foo; # 1 

运算符 #

赋值 #

The = operator works the same across both languages.

The := operator in Raku binds a value to a variable. Binding a variable to another variable gives them the same value and container, meaning mutating attributes of one will mutate the other’s as well. Bound variables cannot be reassigned with = or mutated with ++, --, etc. but they can be bound to another value again:

= 运算符可以跨两种语言相同。

Raku 中的 := 运算符将值绑定到变量。将变量绑定到另一个变量会为它们提供相同的值和容器,这意味着一个变量属性也会改变另一个变量。绑定变量不能被重新分配=或突变++--等,但它们可以被重新绑定到另一个值:

my %map;            # This is a hash, roughly equivalent to a JS object or map 
my %unbound = %map;
my %bound := %map;
%map<foo> = 'bar';
say %unbound;       # {} 
say %bound;         # {foo => bar} 
 
%bound := %unbound;
say %bound;         # {} 

相等 #

Node.js has two equality operators: == and ===.

== is the loose equality operator. When comparing operands with the same type, it will return true if both operands are equal. However, if the operands are different types, they are both cast to their primitives before being compared, meaning these will return true:

Node.js有两个相等运算符:=====

==是松散的平等算子。比较具有相同类型的操作数时,如果两个操作数相等,则返回true。但是,如果操作数是不同的类型,它们在被比较之前都被转换为它们的基元,这意味着它们将返回true:

console.log(1 == 1);   // true
console.log('1' == 1); // true
console.log([] == 0);  // true

Similarly, in Raku, both operands are cast to Numeric before comparison if they don’t share the same type:

类似地,在Raku中,如果它们不共享相同的类型,则在比较之前将两个操作数强制转换为Numeric:

say 1 == 1;       # True 
say '1' == 1;     # True 
say [1,2,3] == 3; # True, since the array has three elements 

The inverse of == is !=.

Raku has another operator similar to ==: eq. Instead of casting operands to Numeric if they’re different types, eq will cast them to strings:

倒数==!=

Raku有另一个类似于的运算符==eq。如果它们是不同的类型,而不是将操作数转换为Numeric,而不是eq将它们转换为字符串:

say '1' eq '1'; # True 
say 1 eq '1';   # True 

The inverse of eq is ne or !eq.

=== is the strict equality operator. This returns true if both operands are the same value. When comparing objects, this will only return true if they are the exact same object:

逆的eqne!eq

===是严格的相等运算符。如果两个操作数是相同的值,则返回true。比较对象时,如果它们是完全相同的对象,则只返回true:

console.log(1 === 1);   // true
console.log('1' === 1); // false
console.log({} === {}); // false
 
let obj = {};
let obj2 = obj;
console.log(obj === obj2); // true;

In Raku, the operator behaves the same, with one exception: two objects that have the same value, but different containers, will return false:

在Raku中,运算符的行为相同,但有一个例外:两个具有相同值但容器不同的对象将返回false:

say 1 === 1; # True 
say '1' === 1; # True 
say {} === {};  # False 
 
my \hash = {};
my %hash = hash;
say hash === %hash; # False 

In the last case it’s the same object, but containers are different, which is why it returns False.

The inverse of === is !==.

This is where Raku’s other equality operators are useful. If the values have different containers, the eqv operator can be used. This operator can be also be used to check for deep equality, which you would normally need to use a library for in Node.js:

在最后一种情况下,它是相同的对象,但容器是不同的,这就是它返回False的原因。

倒数===!==

这是Raku的其他相等运算符很有用的地方。如果值具有不同的容器,则eqv可以使用操作员。此运算符也可用于检查深度相等性,通常需要在Node.js中使用库:

say {a => 1} eqv {a => 1}; # True; 
 
my \hash = {};
my %hash := hash;
say hash eqv %hash; # True 

In the case you need to check if two variables have the same container and value, use the =:= operator.

如果您需要检查两个变量是否具有相同的容器和值,请使用=:=运算符。

my @arr = [1,2,3];
my @arr2 := @arr;   # Bound variables keep the container of the other variable 
say @arr =:= @arr2; # True 

Smartmatching #

Raku has one last operator for comparing values, but it is not exactly an equality operator. This is ~~, the smartmatch operator. This has several uses: it can be used like instanceof in Node.js, to match a regex, and to check if a value is a key in a hash, bag, set, or map:

Raku有一个用于比较值的最后一个运算符,但它不完全是一个相等运算符。这就是~~smartmatch运营商。这有几个用途:它可以像instanceof在Node.js中一样使用,以匹配正则表达式,并检查值是否是散列,包,集或映射中的键:

say 'foo' ~~ Str; # True 
 
my %hash = a => 1;
say 'a' ~~ %hash; # True 
 
my $str = 'abc';
$str ~~ s/abc/def/; # Mutates $str, like foo.replace('abc', 'def') 
say $str;           # def 

While we are talking about instanceof, the equivalent to the constructor property on Node.js objects in Raku is the WHATattribute:

在我们讨论的时候instanceofconstructorRaku中Node.js对象的属性相当于WHAT属性:

console.log('foo'.constructor); // OUTPUT: String
say 'foo'.WHAT; # OUTPUT: Str 

Numeric #

Node.js has +, -, /, *, %, and (in ES6) ** as numeric operators. When the operands are different types, similarly to the equality operators, are cast to their primitives before following through with the operation, making this possible:

Node.js的有+-/*%,和(在ES6)**作为数字运算符。当操作数是不同类型时,类似于相等运算符,在执行操作之前会转换为它们的基元,从而使这成为可能:

console.log(1 + 2);   // 3
console.log([] + {}); // [object Object]
console.log({} + []); // 0

In Raku, again, they are converted to a Numeric type, as before:

在Raku中,它们再次转换为数字类型,如前所述:

say 1 + 2;        # 3 
say [] + {};      # 0 
say {} + [1,2,3]; # 3 

In addition, Raku has div and %%. div behaves like int division in C, while %% checks if one number is cleanly divisible by another or not:

另外,Raku有div%%div表现得像intC中的分裂,同时%%检查一个数字是否可以被另一个数字完全整除:

say 4 div 3; # 1 
say 4 %% 3;  # False 
say 6 %% 3;  # True 

Bitwise #

Node.js has &, |, ^, ~, <<, >>, >>>, and ~ for bitwise operators:

Node.js的有&|^~<<>>>>>,和~对位运算符:

console.log(1 << 1);  // 2
console.log(1 >> 1);  // 0
console.log(1 >>> 1); // 0
console.log(1 & 1);   // 1
console.log(0 | 1);   // 1
console.log(1 ^ 1);   // 0
console.log(~1);      // -2

In Raku, there is no equivalent to >>>. All bitwise operators are prefixed with +, however two’s complement uses +^ instead of ~:

在Raku中,没有相当于>>>。所有按位运算符都以前缀为前缀+,但是使用两个补码+^而不是~

say 1 +< 1; # 2 
say 1 +> 1; # 0 
            # No equivalent for >>> 
say 1 +& 1; # 1 
say 0 +| 1; # 1 
say 1 +^ 1; # 0 
say +^1;    # -2 

Custom operators and operator overloading #

Node.js does not allow operator overloading without having to use a Makefile or build Node.js with a custom version of V8. Raku allows custom operators and operator overloading natively! Since all operators are subroutines, you can define your own like so:

Node.js不允许运算符重载而不必使用Makefile或使用自定义版本的V8构建Node.js. Raku允许自定义操作符和操作符本机重载!由于所有运算符都是子程序,因此您可以像这样定义自己的运算符:

multi sub infix:<||=>($a, $b) is equiv(&infix:<+=>) { $a || $b }
 
my $foo = 0;
$foo ||= 1;
say $foo; # OUTPUT: 1 

Operators can be defined as prefix, infix, or postfix. The is tighter, is equiv, and is looser traits optionally define the operator’s precedence. In this case, ||= has the same precedence as +=.

Note how multi is used when declaring the operator subroutines. This allows multiple subroutines with the same name to be declared while also having different signatures. This will be explained in greater detail in the Functions section. For now, all we need to know is that it allows us to override any native operator we want:

运算符可以定义为prefixinfix,或postfix。的is tighteris equivis 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 sub prefix:<++>($a) is default { $a - 1 }
 
my $foo = 1;
say ++$foo; # OUTPUT: 0 

Control flow #

if/else #

You should be familiar with how if/else looks in JavaScript:

您应该熟悉 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}.`);
}

In Raku, if/else works largely the same, with a few key differences. One, parentheses are not required. Two, else if is written as elsif. Three, the if clause may be written after a statement:

在Raku中,if/的else工作方式基本相同,只有一些关键的区别。一,括号不是必需的。二,else if写成elsif。三,if语句可以声明写出:

my Int $dice-roll = ceiling rand * 12 + ceiling rand * 12;
if $dice-roll == 2 {
    say 'Snake eyes!';
} elsif $dice-roll == 16 {
    say 'Boxcars!';
} else {
    say "Rolled $dice-roll.";
}

Alternatively, though less efficient, this could be written to use if after statements:

或者,虽然效率较低,但可以if在语句后使用:

my Int $dice-roll = ceiling rand * 12 + ceiling rand * 12;
say 'Snake eyes!'        if $dice-roll == 2;
say 'Boxcars!'           if $dice-roll == 16;
say "Rolled $dice-roll." if $dice-roll !~~ 2 | 16;

Raku also has when, which is like if, but if the condition given is true, no code past the when block within the block it’s in is executed:

Raku也有when,就像是if,但是如果给出的条件为真,when那么执行它所执行的块中没有代码超过块:

{
    when True {
        say 'In when block!'; # OUTPUT: In when block! 
    }
    say 'This will never be output!';
}

Additionally, Raku has with, orwith, and without, which are like if, else if, and else respectively, but instead of checking whether their condition is true, they check if it’s defined.

此外,Raku的有withorwithwithout,这是一样ifelse if和,else分别但是,不是检查自己的条件是否为真,他们检查,如果它被定义。

switch #

Switch statements are a way of checking for equality between a given value and a list of values and run some code if one matches. case statements define each value to compare to. default, if included, acts as a fallback for when the given value matches no cases. After matching a case, break is typically used to prevent the code from the cases that follow the one matched from being executed, though rarely this is intentionally omitted.

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;
    }
}

In Raku, given can be used like switch statements. There is no equivalent to break since when blocks are most commonly used like case statements. One major difference between switch and given is that a value passed to a switch statement will only match cases that are exactly equal to the value; given values are smartmatched (~~) against the when values.

在Raku中,given可以像switch语句一样使用。没有相应的,break因为when块最常用于case语句。switch和之间的一个主要区别given是传递给switch语句的值只匹配与值完全相等的情况; given值是~~针对值的smartmatched()when

my     @ranklist = [2, 3, 4, 5, 6, 7, 8, 9, 'Jack', 'Queen', 'King', 'Ace'];
my     @ranks    = @ranklist.pick: 3;
my Int $score    = 0;
 
for @ranks -> $rank {
    # The when blocks implicitly return the last statement they contain. 
    $score += do given $rank {
        when 'Jack' | 'Queen' | 'King' { 10 }
        when 'Ace' { $score <= 11 ?? 10 !! 1 }
        default { $_ }
    };
}

If there are multiple when blocks that match the value passed to given and you wish to run more than one of them, use proceed. succeed may be used to exit both the when block it’s in and the given block, preventing any following statements from being executed:

如果有多个when块与传递的值匹配,given并且您希望运行多个块,请使用proceedsucceed可用于退出when它所在的块和给定的块,防止执行以下任何语句:

given Int {
    when Int     { say 'Int is Int';     proceed }
    when Numeric { say 'Int is Numeric'; proceed }
    when Any     { say 'Int is Any';     succeed }
    when Mu      { say 'Int is Mu'               } # Won't output 
}
 
# OUTPUT: 
# Int is Int 
# Int is Numeric 
# Int is Any 

for, while, and do/while #

There are three different types of for loops in JavaScript:

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 loops most closely resemble for..of loops, since they work on anything as long as it’s iterable. C-style loops are possible to write using loop, but this is discouraged since they’re better written as for loops using ranges. Like if statements, for may follow a statement, with the current iteration being accessible using the $_ variable (known as “it”). Methods on $_ may be called without specifying the variable:

Raku for循环最接近for..of循环,因为只要它是可迭代的,它们就可以处理任何东西。C风格的循环可以使用loop,但不鼓励这样做,因为它们更好地编写为for使用范围的循环。类似if语句,for可以遵循一个语句,当前迭代可以使用$_变量(称为“它”)访问。$_可以在不指定变量的情况下调用方法:

my Str %letters{Str};
%letters{$_} = .uc for 'a'..'z';
.say for %letters.values;
# OUTPUT: 
# A 
# B 
# C 
# etc. 

while loops work identically between JavaScript and Raku. Raku also has until loops, where instead of iterating until the given condition is false, they iterate until the condition is true.

do/while loops are known as repeat/while loops in Raku. Likewise with while, repeat/until loops also exist and loop until the given condition is false.

To write infinite loops in Raku, use loop rather than for or while.

In JavaScript, continue is used to skip to the next iteration in a loop, and break is used to exit a loop early:

while循环在JavaScript和Raku之间的工作相同.Raku也有until循环,而不是迭代直到给定条件为假,它们迭代直到条件为真。

do/while循环repeat/while在Raku 中称为循环。同样whilerepeat/until循环也存在并循环,直到给定条件为假。

要在Raku中编写无限循环,请使用loop而不是forwhile

在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 } 

In Raku, these are known as next and last respectively. There is also redo, which repeats the current iteration without evaluating the loop’s condition again.

next/redo/last statements may be followed by a label defined before an outer loop to make the statement work on the loop the label refers to, rather than the loop the statement is in:

在Raku中,这些分别称为nextlast。还有redo,它重复当前迭代而不再评估循环的条件。

next/ redo/ last语句后跟一个在外部循环之前定义的标签,以使该语句在标签所引用的循环上起作用,而不是该语句所在的循环:

my %primes is SetHash;
my Int $i = 2;
 
OUTSIDE:
repeat {
    next OUTSIDE if $i %% $_ for %primes.keys;
    %primes{$i}++;
} while ++$i < 20;
 
say %primes; # OUTPUT: SetHash(11 13 17 19 2 3 5 7) 

do #

do is not currently a feature in JavaScript, however a proposal has been made to add it to ECMAScript. do expressions evaluate a block and return the result:

do目前不是JavaScript中的一项功能,但已提出将其添加到ECMAScript的提案。do表达式计算一个块并返回结果:

constant VERSION        = v2.0.0;
constant VERSION_NUMBER = do {
    my @digits = VERSION.Str.comb(/\d+/);
    :16(sprintf "%02x%02x%04x", |@digits)
};
say VERSION_NUMBER; # OUTPUT: 33554432 

Types #

Creating types #

In JavaScript, types are created by making a class (or a constructor in ES5 and earlier). If you’ve used TypeScript, you can define a type as a subset of other types like so:

在JavaScript中,通过创建类(或ES5及更早版本中的构造函数)来创建类型。如果您使用过TypeScript,则可以将类型定义为其他类型的子集,如下所示:

type ID = string | number;

In Raku, classes, roles, subsets, and enums are considered types. Creating classes and roles will be discussed in the OOP section of this article. Creating an ID subset can be done like so:

在Raku中,类,角色,子集和枚举被视为类型。创建类和角色将在本文的OOP部分中讨论。创建ID子集可以这样完成:

subset ID where Str | Int;

See the documentation on subset and Junction for more information.

TypeScript enums may have numbers or strings as their values. Defining the values is optional; by default, the value of the first key is 0, the next key, 1, the next, 2, etc. For example, here is an enum that defines directions for extended ASCII arrow symbols (perhaps for a TUI game):

有关更多信息,请参阅子集连接的文档。

TypeScript枚举可以包含数字或字符串作为其值。定义值是可选的; 默认情况下,第一个键的值为0,下一个键为1,下一个键为2,等等。例如,这是一个枚举,用于定义扩展ASCII箭头符号的方向(可能用于TUI游戏):

enum Direction (
    UP    = '↑',
    DOWN  = '↓',
    LEFT  = '←',
    RIGHT = '→'
);

Enums in Raku may have any type as their keys’ values. Enum keys (and optionally, values) can be defined by writing enum, followed by the name of the enum, then the list of keys (and optionally, values), which can be done using < >, « », or ( ). ( ) must be used if you want to define values for the enum’s keys. Here is the Direction enum as written in Raku:

Raku中的枚举可以使用任何类型作为其键值。枚举键(以及可选的值)可以通过写入来定义enum,然后是枚举的名称,然后是键列表(以及可选的值),可以使用<>«»()来完成( )如果要为枚举键定义值,则必须使用。这是Raku中编写的Direction枚举:

enum Direction (
    UP    => '↑',
    DOWN  => '↓',
    LEFT  => '←',
    RIGHT => '→'
);

See the documentation on enum for more information.

有关更多信息,请参阅枚举文档。

Using types #

In TypeScript, you can define the type of variables. Attempting to assign a value that doesn’t match the type of the variable will make the transpiler error out. This is done like so:

在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;
};

In Raku, variables can be typed by placing the type between the declarator (my, our, etc.) and the variable name. Assigning a value that doesn’t match the variable’s type will throw either a compile-time or runtime error, depending on how the value is evaluated:

在Raku中,变量可以通过将说明符(之间的类型被键入myour等)和变量名。分配与变量类型不匹配的值将引发编译时或运行时错误,具体取决于值的计算方式:

enum Name <Phoebe Daniel Joe>;
my Str $name = 'Phoebe';
$name = Phoebe; # Throws a compile-time error 
 
# The type here defines the type of the elements of the array. 
my Str @hobbies = ['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 %todo{Str} = (
    'clean the bathroom' => False,
    'walk the dog'       => True,
    'wash the dishes'    => True
);
 
# The type here defines the return value of the routine. 
my Bool &do-job = sub (Str $job --> Bool) {
    %todo{$job} = True;
};

比较 JavaScript 和 Raku 的类型 #

Here is a table of some JavaScript types and their equivalents in Raku:

以下是Raku中一些JavaScript类型及其等价物的表格:

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 is both a superclass of all types in JavaScript and a way to create a hash. In Raku, Mu is a superclass of all types, though usually you want to use Any instead, which is a subclass of Mu but also a superclass of nearly every type, with Junction being an exception. When using Object as a hash, Hash is what you want to use.

There are three types equivalent to Array. Array is most similar to Array, since it acts as a mutable array. List is similar to Array, but is immutable. Seq is used to create lazy arrays.

String and Str are for the most part used identically.

There are several different types in Raku equivalent to Number, but the three you’ll most commonly see are Int, Num, and Rat. Intrepresents an integer. Num represents a floating-point number, making it the most similar to Number. Rat represents a fraction of two numbers, and is used when Num cannot provide precise enough values.

Boolean and Bool are for the most part used identically.

Map has both a mutable and an immutable equivalent in Raku. Map is the immutable one, and Hash is the mutable one. Don’t get them mixed up! Like Map in JavaScript, Map and Hash can have any type of key or value, not just strings for keys.

Like Map, Set also has both a mutable and an immutable equivalent in Raku. Set is the immutable one, and SetHash is the mutable one.

Object既是JavaScript中所有类型的超类,也是创建哈希的方法。在Raku中,是所有类型的超类,尽管通常要使用任何代替,这是的一个子类Mu,而且几乎所有类型的超类,与接线是一个例外。当Object用作哈希时,哈希就是你想要使用的。

有三种类型相当于Array数组最相似Array,因为它充当可变数组。列表类似于Array,但是是不可变的。Seq用于创建惰性数组。

StringStr在大多数情况下使用相同。

Raku中有几种不同的类型相当于Number,但你最常见的三种是IntNumRatInt表示整数。Num表示一个浮点数,使其最相似NumberRat表示两个数字的一小部分,并且在Num无法提供足够精确的值时使用。

BooleanBool在大多数情况下使用相同。

Map既具有可变的,并且在Raku的不可变等效地图是不可变的一个,并且哈希是可变的一个。不要混淆他们!就像Map在JavaScript中,Map并且Hash可以有任何类型的键或值,而不仅仅是钥匙串。

MapSet也都一个可变的和Raku中一个不变的等效设置是不可变的一个,并且SetHash是可变的。

函数 #

# TBD

面向对象编程

# TBD

异步编程

# TBD

网络 API

网络 #

In Raku, there are two APIs for dealing with networking: IO::Socket::INET (for synchronous networking), and IO::Socket::Async (for asynchronous networking).

IO::Socket::INET currently only supports TCP connections. Its API resembles that of C’s socket API. If you’re familiar with that, then it won’t take long to understand how to use it. For example, here’s an echo server that closes the connection after receiving its first message:

在Raku中,有两个用于处理网络的API :( IO::Socket::INET用于同步网络)和IO::Socket::Async(用于异步网络)。

IO::Socket::INET目前只支持TCP连接。它的API类似于C的套接字API。如果您熟悉它,那么理解如何使用它不会花费很长时间。例如,这是一个echo服务器,它在收到第一条消息后关闭连接:

my IO::Socket::INET $server .= new:
    :localhost<localhost>,
    :localport<8000>,
    :listen;
 
my IO::Socket::INET $client .= new: :host<localhost>, :port<8000>;
$client.print: 'Hello, world!';
 
my IO::Socket::INET $conn = $server.accept;
my Str $msg               = $conn.recv;
say $msg; # OUTPUT: Hello, world! 
$conn.print($msg);
 
say $client.recv; # OUTPUT: Hello, world! 
$conn.close;
$client.close;
$server.close;

By default, IO::Socket::INET connections are IPv4 only. To use IPv6 instead, pass :family(PF_INET6) when constructing a server or a client.

In contrast, IO::Socket::Async supports both IPv4 and IPv6 without the need to specify which family you wish to use. It also supports UDP sockets. Here’s how you would write the same echo server as above asynchronously (note that Supply.tap is multithreaded; if this is undesirable, use Supply.act instead:

默认情况下,IO::Socket::INET连接仅限IPv4。要使用IPv6,请:family(PF_INET6)在构建服务器或客户端时传递。

相反,IO::Socket::Async支持IPv4和IPv6,无需指定要使用的族。它还支持UDP套接字。以下是如何异步编写与上面相同的echo服务器(请注意,这Supply.tap是多线程的;如果这是不合需要的,请Supply.act改用:

my $supply = IO::Socket::Async.listen('localhost', 8000);
my $server = $supply.tap(-> $conn {
    $conn.Supply.tap(-> $data {
        say $data; # OUTPUT: Hello, world! 
        await $conn.print: $data;
        $conn.close;
    })
});
 
my $client = await IO::Socket::Async.connect('localhost', 8000);
$client.Supply.tap(-> $data {
    say $data; # OUTPUT: Hello, world! 
    $client.close;
    $server.close;
});
 
await $client.print: 'Hello, world!';

The equivalent code in Node.js looks like this:

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 doesn’t natively support HTTP/HTTPS. However, CPAN packages such as Cro help fill the gap.

Raku本身不支持HTTP / HTTPS。然而,像Cro这样的CPAN包填补了这个空白。

DNS #

Raku does not currently support the majority of the features that Node.js’s DNS module implements. IO::Socket::INET and IO::Socket::Async can resolve hostnames, but features like resolving DNS records and reverse IP lookups are not implemented yet. There are some modules that are a work in progress, such as Net::DNS::BIND::Manage, that aim to improve DNS support.

Raku目前不支持Node.js的DNS模块实现的大多数功能。IO::Socket::INET并且IO::Socket::Async可以解析主机名,但尚未实现解析DNS记录和反向IP查找等功能。有些模块正在进行中,例如Net :: DNS :: BIND :: Manage,旨在改善DNS支持。

Punycode #

Punycode support is available through the Net::LibIDN, Net::LibIDN2, and IDNA::Punycode modules on CPAN.

通过CPAN上的Net :: LibIDNNet :: LibIDN2IDNA :: Punycode模块可以获得Punycode支持。

文件系统 API

# TBD

模块和包

# TBD