Javascript (Node) to Perl 6 - nutshell

Learning Perl 6 from Node.js, in a nutshell

This page attempts to provide a way for users experienced in Node.js to learn Perl 6. 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 Perl 6; this is a reference for users who are already at an intermediate to advanced skill level with Node.js.

Basic syntax

"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:

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

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

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

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

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

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 Perl 6:

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 Perl 6 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.

Variables

Variables in Node.js can be defined like this;

var foo = 1;    // Lexically scoped with functions and modules
let foo = 1;    // Lexically scoped with blocks
const foo = 1;  // Lexically scoped with blocksconstant
 
global.foo = 1// Dynamically scopedglobal
foo = 1;        // Dittobut implicitforbidden in strict mode

In Perl 6 there is no equivalent to var. An important note to make is that there is no variable hoisting in Perl 6; 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 Perl 6:

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):

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

Perl 6 also allows this:

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

Operators

Assignment

The = operator works the same across both languages.

The := operator in Perl 6 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:

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

Equality

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:

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

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

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

The inverse of == is !=.

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

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:

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

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

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

The inverse of === is !==.

This is where Perl 6'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:

say {=> 1} eqv {=> 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

Perl 6 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:

say 'foo' ~~ Str# True 
 
my %hash = => 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 typeof or the constructor property on Node.js objects in Perl 6 is the ^name meta-attribute:

console.log(typeof 'foo');      // string
console.log('foo'.constructor); // String
say 'foo'.^name# 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:

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

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

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

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

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

Bitwise

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

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 Perl 6, there is no equivalent to >>>. All bitwise operators are prefixed with +, however two's complement uses +^ instead of ~:

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. Perl 6 allows custom operators and operator overloading natively! Since all operators are subroutines, you can define your own like so:

multi sub infix:<||=>($a$bis equiv(&infix:<+=>{ $a || $b }
 
my $foo = 0;
$foo ||= 1;
say $foo# 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:

# 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:<++>($ais default { $a - 1 }
 
my $foo = 1;
say ++$foo# 0 

Conditionals

# TBD

Control flow

# TBD

Functions

# TBD

Types

# TBD

Object-oriented programming

# TBD

The networking API

Net

In Perl 6, 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:

my IO::Socket::INET $server .= new:
    :localhost<localhost>,
    :localport<8000>,
    :listen;
 
my IO::Socket::INET $client .= new: :host<localhost>:port<8000>;
$client.print: 'ayy lmao';
 
my IO::Socket::INET $conn = $server.accept;
my Str $msg               = $conn.recv;
say $msg# RESULT: ayy lmao 
$conn.print($msg);
 
say $client.recv# RESULT: ayy lmao 
$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:

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

The equivalent code in Node.js looks like this:

const net = require('net');
 
const server = net.createServer(conn => {
    conn.setEncoding('utf8');
    conn.on('data'data => {
        console.log(data); # RESULT: ayy lmao 
        conn.write(data);
        conn.end();
    });
}).listen(8000'localhost');
 
const client = net.createConnection(8000'localhost', () => {
    client.setEncoding('utf8');
    client.on('data'data => {
        console.log(data); # RESULT: ayy lmao 
        client.end();
        server.close();
    });
    client.write("ayy lmao");
});

HTTP/HTTPS

Perl 6 doesn't natively support HTTP/HTTPS. However, CPAN packages such as Cro help fill the gap.

DNS

Perl 6 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.

Punycode

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

The filesystem API

# TBD

Modules and packages

# TBD