I'm trying to create a variable in another namespace with interpolation and haven't been able to get it to work after consulting https://docs.raku.org/language/packages#index-entry-::(). While testing different things, I discovered something that has me puzzled. This works:
#!/usr/bin/env raku
$Foo::bar = 'foobar';
say $Foo::bar;
$ ./package.raku
foobar
but this doesn't:
#!/usr/bin/env raku
my $bar = 'bar';
$Foo::($bar) = 'foobar';
say $Foo::bar;
$ ./package-interpolate.raku
No such symbol '$bar'
in block <unit> at ./package-interpolate.raku line 4
Am I missing something? Thanks!
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>
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
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 packageFoo::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')
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.
Creating variables in other namespaces using interpolation seems impossible in Raku. It is possible to access them this way if they are in the lexical scope beforehand. If you add this part before your code it works:
class Foo {
our $bar = 'our $bar';
}
To achieve what you want to do, I would suggest to check this section of rosettacode: https://rosettacode.org/wiki/Add_a_variable_to_a_class_instance_at_runtime#Raku
It presents a few different approaches to this problem with Raku. Even if in their code the concept of "namespace" is reduced to classes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With