Can't Interpolate Variable In Another Namespace (Raku)

TL;DR There are three distinct syntaxes for a reference to a variable (symbol) held in some particular package (namespace). You tried two, but it looks like you may need the third. There are some wrinkles here too, so this answer is long.


Here's a summary of the three syntaxes, starting with the one that might be (part of) what you want, followed by the two you tried:


  • Foo::Bar::Baz::<$qux>

    Statically specified packages, dynamically specified variable

    If the nested package specification fails to resolve to a corresponding set of existing packages, then the construct generates an error. If the package specification resolves, then the variable is created if it is assigned or bound to and doesn't already exist.


  • $Foo::Bar::Baz::qux

    Statically specified packages and variable

    If any of the packages in the nested package specification fail to resolve to an existing package, that package is automatically created. The variable is also created if it is assigned or bound to and doesn't already exist.


  • ::('$Foo::Bar::Baz::qux')

    Dynamically specified packages and variable

    If the nested package specification fails to resolve to a corresponding set of existing packages, or if the variable does not exist, then the construct generates an error.


The rest of this answer is three sections with more details corresponding to the above.

Foo::Bar::Baz::<$qux>

Staticically specified packages, dynamically specified variable

  • If the packages all already exist, the construct will work, creating the variable if it doesn't already exist. Otherwise the construct will generate an error, either a reasonable one at compile-time (if only one package is specified) or an LTA one at run-time (if nested packages are specified).

  • The variable name can be specified indirectly via an expression/variable whose value is evaluated at run-time.


Quoting the doc linked in the title of this section:

To do direct lookup in a package's symbol table without scanning, treat the package name as a hash

Here's a first example in which we reference a non-existent variable in a non-nested non-existent package:

my $bar = '$baz';
say Foo::{$bar};

yields the compile time error:

Undeclared name:
    Foo used at line 2

Next, a non-existent variable in a set of nested non-existent packages:

my $bar = '$baz';
try say Foo::Bar::Baz::{$bar};
say $!;               # Could not find symbol '&Baz' in 'GLOBAL::Foo::Bar'
say GLOBAL::Foo::Bar; # (Bar)

The error message (the exception held in $!) isn't just LTA, but reflects craziness. And the construct has created the package GLOBAL::Foo::Bar. More madness!


Now an example in which we reference a package that does exist and a variable within it that does not initially exist:

package Foo {}
my $bar = '$qux';
say Foo::{$bar};              # (Any)
say Foo::{$bar}:exists;       # False
Foo::{$bar} = 99;
say Foo::{$bar}:exists;       # True
say Foo::{$bar};              # 99
say Foo.WHO.keys;             # ($qux)
say $Foo::qux;                # 99

So the Foo::{...} syntax (or Foo::<...>, but not ::(...)) does not error out if the specified variable does not exist, but instead only if the package does not exist.

A reference in this syntax to a variable that does not exist (in a package that does) returns an (Any). Iff that (Any) is assigned to, it creates the variable.

(Unless the package is MY or unspecified, which means the same thing in this syntax. If a variable looked up in MY is missing the lookup will return the value Nil instead of (Any), and cannot be assigned or bound to.)


$Foo::Bar::Baz::qux

Statically specified packages and variable

  • If you mention a package using this syntax, even a non-nested one, then you will create it if it doesn't exist.

  • If you assign/bind to a variable, you will create it if it doesn't already exist.

  • You must statically state both the (nested) package(s) you want to specify and the variable name. You cannot indirectly state (eg via a variable) any part of this syntax.


Quoting the doc linked in the title of this section:

Ordinary package-qualified names look like this: $Foo::Bar::quux, which would be the $quux variable in package Foo::Bar

Here's the first fragment of code from your question (that works, to @user0721090601's surprise) with some lines appended. The block of three lines show where the Foo package landed (and provide a sneak peek ahead at the three syntaxes covered in this answer and in the doc). The last line confirms the $bar variable has been added to the Foo package:

$Foo::bar = 'foobar';
say $Foo::bar;             # foobar

say GLOBAL::Foo;           # (Foo)    Package qualified name yields type object `(Foo)`
say GLOBAL::<Foo>;         # (Foo)    Package qualified lookup (yields same object)
say ::('Foo');             # (Foo)    Package scanning lookup (yields same object)

say GLOBAL::Foo.WHO.keys;  # ($bar)   `.WHO` returns `(Foo)`'s package named `Foo`

GLOBAL is the "pseudo" package that contains "Interpreter-wide package symbols, really UNIT::GLOBAL".


If you remove the first line in the above code ($Foo::bar = 'foobar';):

  • The result for the say $Foo::bar; line is (Any);

  • The block of three added lines continue to display (Foo), demonstrating that a mere mention of a variable in the syntactic form $Foo::bar is enough to create the package Foo;

  • The last line displays (), demonstrating that $bar is not added to the Foo package despite the say $Foo::bar; line displaying (Any) rather than Nil.


::('$Foo::Bar::Baz::qux')

Dynamically specified packages and variable

  • If you wish to read or write existing variables in existing packages, without the risk of accidentally creating them if they don't exist, use this syntax.

  • Conversely, if you wish to automatically create packages and/or variables, use one of the other two syntaxes or some other approach.

  • You can use complex expressions in the (...) part of this syntax and they will be dynamically interpolated but then treated as if they were static source code. Thus you can do things like including the literal string '$Foo::Bar::Baz::qux' in the (...), or a variable that evaluates to such a string, and the compiler will interpolate it into the overall reference that is then used to guide the lookup, treating each component between the ::s as a distinct nested package.


Quoting the doc linked in the title of this section:

using ::($expr) where you'd ordinarily put a package or variable name ... the indirect name is looked up ... with priority given first to leading pseudo-package names, then to names in the lexical scope (searching scopes outwards, ending at CORE). The current package is searched last.

Note that the lookup will scan through many packages.


The second fragment of code in your question (that does not work as you expected) uses this scanning logic for variable lookup, possibly looking in not just one explicitly specified package but instead many packages.

Here's the same code, but with another line inserted at the start:

package Foo { our $bar }     # <-- This line added
my $bar = 'bar';
$Foo::($bar) = 'foobar';
say $Foo::bar;               # foobar

Now it works.

This shows that your $Foo::($bar) = 'foobar'; line:

  • Looked for a variable $bar in a package Foo. (So "interpolating into names" does work "as advertised" by the doc.)

  • But did not create a package.

  • And did not create a variable.

  • But did do the assignment because the lookup successfully found a package and variable matching the lookup.