DEV Community

Cover image for Not So Obvious
 Semantic Changes (Part 1 of 3)
Elizabeth Mattijsen
Elizabeth Mattijsen

Posted on • Edited on

Not So Obvious
 Semantic Changes (Part 1 of 3)

Apart from the visible changes between Perl and Raku, there are a small number of semantic changes in code that otherwise is identical between Perl and Raku. The following three blog posts elaborate on these potential gotcha’s.

Everything is Lexical in Raku

This simple statement is easy to understand, but for the experienced Perl programmer, maybe not all implications are immediately clear.

For instance, if one uses a module in Perl that imports symbols, like Test::More, the imported symbols become available in the entire compilation unit:

# Perl
{                          # create a scope
    use Test::More;        # use module inside scope
    ok 1, “test passes”;   # ok 1 - test passes
}
# all of the imported subs are available outside:
ok 1, “test also passes”;  # ok 1 - test also passes
Enter fullscreen mode Exit fullscreen mode

Now compare this to the situation in Raku:

# Raku
{                          # create a scope
    use Test;              # use module inside scope
    ok 1, “test passes”;   # ok 1 - test passes
}
Enter fullscreen mode Exit fullscreen mode

Inside the scope, the ok sub is known, and can be executed. However if we want to call the ok sub outside of the scope, then this will cause a compilation error as the sub is not known outside of the lexical scope:

# Raku
{                          # create a scope
    use Test;              # use module inside scope
}
ok 1, "This test passes";
===SORRY!=== Error while compiling …
Undeclared routine:
    ok used at line …
Enter fullscreen mode Exit fullscreen mode

Now, this in itself could be considered a not so very useful feature. Because in Perl, it is not possible to have multiple versions of the same module installed at the same time (unless you start playing really nasty tricks with -I or use lib, which does not improve maintainability or stability).

In Raku however, it is possible to have multiple versions of the same module installed. So suppose you have two versions of module Foo installed: version 1.21 and version 2.0. Each of which exports a subroutine called read. You can then provide easy access to either by localising the use statements inside separate subroutines:

# Raku
sub old-read($file) {
    use Foo:version<1.21>;
    read($file)
}
sub new-read($file) {
    use Foo:version<2.0>;
    read($file)
}
Enter fullscreen mode Exit fullscreen mode

This feature is enormously powerful tool for version management of data. And it makes life of system admins a lot easier, as you can freeze dependencies in the code, so that you never have to worry whether a running system will break by adding newer versions of already installed modules.

Global Variables Vs. Dynamic Variables

Both Perl and Raku have a my statement to define lexical variables. Both Perl and Raku have our to define variables in a namespace that should be accessible from outside of that namespace.

So this works in Perl and Raku:

# Raku and Perl
package A {
    our $a = 42;
}
say $A::a;  # 42
Enter fullscreen mode Exit fullscreen mode

However, global variables in a threaded environment such as Raku, are generally a bad idea. Therefore, Raku has an intermediate form of variables that can be lexically defined, but which have global accessibility / discoverability: they are called "dynamic variables".

Dynamic variables can be recognised by the * twigil (secondary sigil). For instance, where Perl has STDOUT for the IO handle for standard output, Raku has $*OUT.

Perhaps this is better explained with an example: in Perl, STDOUT can be used as an object and you can call methods on it, e.g. say:

# Perl
STDOUT->say("hello world");  # hello world
Enter fullscreen mode Exit fullscreen mode

You can do the same in Raku with $*OUT:

# Raku
$*OUT.say("hello world");    # hello world
Enter fullscreen mode Exit fullscreen mode

Which is what the say command does under the hood.

So much for the similarities. What if one wants to override standard output for a given scope? Since STDOUT is a global variable in Perl, one cannot do much about this, other than using the local functionality of Perl. But this is really not recommended.

In Raku, one defines a lexical with the same name and gives it a value:

# Raku
say "goodbye";               # goodbye
{
    my $*OUT = open("out",:w);  # STDOUT writes to file "out"
    say "cruel";                # written to file
}
say "world";                 # world
say "out".IO.slurp;          # cruel
Enter fullscreen mode Exit fullscreen mode

Note that say command inside the scope sees the alternate version of $*OUT: the visibility is dynamic (looking up the execution stack for lexically defined dynamic variables), and therefore writes the string "cruel" to the file rather than showing it on standard output.

Almost all system variables in Raku are dynamic variables. Let’s compare a few between Perl and Raku:

   description                  Perl            Raku
   -----------------------------------------------------------
   standard input               STDIN           $*IN
   standard output              STDOUT          $*OUT
   standard error output        STDERR          $*ERR
   environment variable         %ENV            %*ENV
   raw command line arguments   @ARGV           @*ARGS
   current directory            cwd (use Cwd)   $*CWD
   current process ID           $$              $*PID
   module loading management    @INC            $*REPO
   name of executing program    $0              $*PROGRAM-NAME
   -----------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

Note that the dynamic variable of Raku are generally more descriptive (and longer) than the Perl equivalents.

Summary

In this blog posts it was shown that everything in Raku is lexically scoped. And that in Raku dynamic variables take the ecological niche that global variables occupy in Perl.

Top comments (0)