Is it possible to introspect into the scope of a Scalar at runtime? - raku

If I have the following variables
my $a = 0;
my $*b = 1;
state $c = 2;
our $d = 3;
I can easily determine that $*b is dynamic but $a is not with the following code
say $a.VAR.dynamic;
say $*b.VAR.dynamic;
Is there any way to similarly determine that $c is a state variable and $d is a package-scoped variable? (I know that I could do so with a will trait on each variable declaration, but I'm hopping there is a way that doesn't require annotating every declaration. Maybe something with ::(...) interpolation?)

In the case of the package-scoped variable, not too hard:
our $foo = 'bar';
say $foo.VAR.name ∈ OUR::.keys
where we're using the OUR pseudopackage. However, there's no such thing as a STATE pseudopackage. They obviously show up in the LEXICAL pseudopackage, but I can't find a way to check if they're a state variable or not. Sorry.

To my knowledge, there is no way to recognize a state variable. Like any lexical, it lives in the lexpad. The only thing different about it, is that it effectively has code generated to do the initialization the first time the scope is entered.

As Elizabeth Mattijsen correctly noted, it is currently not possible to see whether a variable is a state variable at run time. ... at least technically at runtime.
However, as Jonathan Worthington's comment implies, it is possible to check this at compile time. And, absent deep meta-programming shenanigans, whether a variable is a state variable is immutable after compile-time. And, of course, it's possible to make note of some info at compile time and then use it at runtime.
Thus, it's possible to know, at runtime, whether a variable is a state one with (compile-time) code along the following lines, which provides a list-state-vars trait that lists all the state variables in a function:
multi trait_mod:<is>(Sub \f, :$list-state-vars) {
use nqp;
given f.^attributes.first({.name eq '#!compstuff'}).get_value(f)[0] {
say .list[0].list.grep({try .decl ~~ 'statevar'}).map({.name});
}
};
This code is obviously pretty fragile/dependent on the Rakudo implementation details of QAST. Hopefully this will be much easier with RAST, but this basic approach is already workable and, in the meantime, this guide to QAST hacking is a helpful resource for this sort of meta programming.

Related

Variable re-assign method result if not Nil

Is there idiomatic way of applying and assigning object variable method call, but only if it's defined (both method and the result)?
Like using safe call operator .? and defined-or operator //, and with "DRY principle" – using the variable only once in the operation?
Like this (but using another variable feels like cheating):
my $nicevariable = "fobar";
# key step
(my $x := $nicevariable) = $x.?possibly-nonexistent-meth // $x;
say $nicevariable; # => possibly-nonexistent-meth (non-Nil) result or "foobar"
... And avoiding andthen, if possible.
Are you aware of the default trait on variables?
Not sure if it fits your use case, but $some-var.?unknown-method returns Nil, so:
my $nicevariable is default("fobar");
$nicevariable = $nicevariable.?possibly-nonexistent-meth;
say $nicevariable; # fobar
results in $nicevariable being reset to its default value;
I'm not entirely sure what you mean by "using the variable only once in the operation". If Liz's answer qualifies, then it's probably the cleaner way to go.
If not, here's a different approach that avoids naming the variable twice:
my $nicevariable = "foobar";
$nicevariable.=&{.^lookup('possibly-nonexistent-meth')($_)}
This is a bit too cryptic for my tastes; here's what it does: If the method exists, then that's similar to¹ calling &method($nicevariable), which is the same as $nicevariable.method. If the method does not exist, then it's like calling Mu($nicevariable) – that is, coercing $nicevariable into Mu or a subtype of Mu. But since everything is already a subtype of Mu, that's a no-op and just returns $nicevariable.
[1]: Not quite, since &method would be a Sub, but basically.
EDIT:
Actually, that was over-complicating things. Here's a simpler version:
my $nicevariable = "foobar";
$nicevariable.=&{.?possibly-nonexistent-meth // $_}
Not sure why I didn't just have that to begin with…
Assuming the method returns something that is defined, you could do (if I'm understanding the question correctly):
my $x = "foobar";
$x = $_ with $y.?possibly-nonexistent-meth;
$x will remain unchanged if the method didn't exist (or it did exist and returned a type object).
As of this merge you can write:
try { let $foo .= bar }
This is a step short of what I think would have been an ideal solution, which is unfortunately a syntax error (due to a pervasive weakness in Raku's current grammar that I'm guessing is effectively unsolvable, much as I would love it to be solved):
{ let $foo .?= bar } # Malformed postfix call...
(Perhaps I'm imagining things, but I see a glimmer of hope that the above wrinkle (and many like it) will be smoothed over a few years from now. This would be after RakuAST lands for Raku .e, and the grammar gets a hoped for clean up in Raku .f or .g.)
Your question's title is:
Variable re-assign method result if not Nil
My solution does a variable re-assign method if not undefined, which is more general than just Nil.
Then again, your question's body asks for exactly that more general solution:
Is there idiomatic way of applying and assigning object variable method call, but only if it's defined (both method and the result)?
So is my solution an ideal one?
My solution is not idiomatic. But that might well be because of the bug I found, now solved with the merge linked at the start of my answer. I see no reason why it should not become idiomatic, once it's in shipping Rakudos.
The potentially big issue is that the try stores any exception thrown in $! rather than letting it blow up. Perhaps that's OK for a given use case; perhaps not.
Special thanks to you for asking your question, which prompted us to come up with various solutions, which led me to file an issue, which led vrurg to both analyse the problem I encountered and then fix it. :)

Mixing-in roles in traits apparently not working

This example is taken from roast, although it's been there for 8 years:
role doc { has $.doc is rw }
multi trait_mod:<is>(Variable $a, :$docced!) {
$a does doc.new(doc => $docced);
}
my $dog is docced('barks');
say $dog.VAR;
This returns Any, without any kind of role mixed in. There's apparently no way to get to the "doc" part, although the trait does not error. Any idea?
(This answer builds on #guifa's answer and JJ's comment.)
The idiom to use in variable traits is essentially $var.var.VAR.
While that sounds fun when said aloud it also seems crazy. It isn't, but it demands explanation at the very least and perhaps some sort of cognitive/syntactic relief.
Here's the brief version of how to make some sense of it:
$var makes sense as the name of the trait parameter because it's bound to a Variable, a compiler's-eye view of a variable.
.var is needed to access the user's-eye view of a variable given the compiler's-eye view.
If the variable is a Scalar then a .VAR is needed as well to get the variable rather than the value it contains. (It does no harm if it isn't a Scalar.)
Some relief?
I'll explain the above in more detail in a mo, but first, what about some relief?
Perhaps we could introduce a new Variable method that does .var.VAR. But imo this would be a mistake unless the name for the method is so good it essentially eliminates the need for the $var.var.VAR incantation explanation that follows in the next section of this answer.
But I doubt such a name exists. Every name I've come up with makes matters worse in some way. And even if we came up with the perfect name, it would still barely be worth it at best.
I was struck by the complexity of your original example. There's an is trait that calls a does trait. So perhaps there's call for a routine that abstracts both that complexity and the $var.var.VAR. But there are existing ways to reduce that double trait complexity anyway, eg:
role doc[$doc] { has $.doc is rw = $doc}
my $dog does doc['barks'];
say $dog.doc; # barks
A longer explanation of $var.var.VAR
But $v is already a variable. Why so many var and VARs?
Indeed. $v is bound to an instance of the Variable class. Isn't that enough?
No, because a Variable:
Is for storing metadata about a variable while it's being compiled. (Perhaps it should have been called Metadata-About-A-Variable-Being-Compiled? Just kidding. Variable looks nice in trait signatures and changing its name wouldn't stop us needing to use and explain the $var.var.VAR idiom anyway.)
Is not the droid we are looking for. We want a user's-eye view of the variable. One that's been declared and compiled and is then being used as part of user code. (For example, $dog in the line say $dog.... Even if it were BEGIN say $dog..., so it ran at compile-time, $dog would still refer to a symbol that's bound to a user's-eye view container or value. It would not refer to the Variable instance that's only the compiler's-eye view of data related to the variable.)
Makes life easier for the compiler and those writing traits. But it requires that a trait writer accesses the user's-eye view of the variable to access or alter the user's-eye view. The .var attribute of the Variable stores that user's-eye view. (I note the roast test has a .container attribute that you omitted. That's clearly now been renamed .var. My guess is that that's because a variable may be bound to an immutable value rather than a container so the name .container was considered misleading.)
So, how do we arrive at $var.var.VAR?
Let's start with a variant of your original code and then move forward. I'll switch from $dog to #dog and drop the .VAR from the say line:
multi trait_mod:<is>(Variable $a, :$docced!) {
$a does role { has $.doc = $docced }
}
my #dog is docced('barks');
say #dog.doc; # No such method 'doc' for invocant of type 'Array'
This almost works. One tiny change and it works:
multi trait_mod:<is>(Variable $a, :$docced!) {
$a.var does role { has $.doc = $docced }
}
my #dog is docced('barks');
say #dog.doc; # barks
All I've done is add a .var to the ... does role ... line. In your original, that line is modifying the compiler's-eye view of the variable, i.e. the Variable object bound to $a. It doesn't modify the user's-eye view of the variable, i.e. the Array bound to #dog.
As far as I know everything now works correctly for plural containers like arrays and hashes:
#dog[1] = 42;
say #dog; # [(Any) 42]
say #dog.doc; # barks
But when we try it with a Scalar variable:
my $dog is docced('barks');
we get:
Cannot use 'does' operator on a type object Any.
This is because the .var returns whatever it is that the user's-eye view variable usually returns. With an Array you get the Array. But with a Scalar you get the value the Scalar contains. (This is a fundamental aspect of P6. It works great but you have to know it in these sorts of scenarios.)
So to get this to appear to work again we have to add a couple .VAR's as well. For anything other than a Scalar a .VAR is a "no op" so it does no harm to cases other than a Scalar to add it:
multi trait_mod:<is>(Variable $a, :$docced!) {
$a.var.VAR does role { has $.doc = $docced }
}
And now the Scalar case also appears to work:
my $dog is docced('barks');
say $dog.VAR.doc; # barks
(I've had to reintroduce the .VAR in the say line for the same reason I had to add it to the $a.var.VAR ... line.)
If all were well that would be the end of this answer.
A bug
But something is broken. If we'd attempted to initialize the Scalar variable:
my $dog is docced('barks') = 42;
we'd see:
Cannot assign to an immutable value
As #guifa noted, and I stumbled on a while back:
It seems that a Scalar with a mixin no longer successfully functions as a container and the assignment fails. This currently looks to me like a bug.
Not a satisfactory answer but maybe you can progress from it
role doc {
has $.doc is rw;
}
multi trait_mod:<is>(Variable:D $v, :$docced!) {
$v.var.VAR does doc;
$v.var.VAR.doc = $docced;
}
say $dog; # ↪︎ Scalar+{doc}.new(doc => "barks")
say $dog.doc;  # ↪︎ barks
$dog.doc = 'woofs'; #
say $dog; # ↪︎ Scalar+{doc}.new(doc => "woofs")
Unfortunately, there is something off with this, and applying the trait seems to cause the variable to become immutable.

Sigilless variables, constants, bindings: when to choose what

So, from the documentation is almost clear what the three entities from the title are, but it is not very clear what their purpose is.
Constants are common in many languages. You don't want to write 3.14 all over your code and that's why you define a constant PI that, by the nature of the thing it represents, cannot be changed and that's why its value is constant in time.
Defining a variable bound to another entity with := is also almost clear but not really. If I bind a variable to 3.14, isn't it the same as defining a PI constant? Is $a := $b actually defining $a as an alias to $b or, as some other languages call it, a reference? Also, references are generally used in some languages to make it clear, in object construction or function calls, that you don't want to copy an object around, but why would it be useful, in the same scope, to have
$number_of_cakes = 4;
$also_the_number_of_cakes := $number_of_cakes;
?
Finally, the documentation explains how one can define a sigilless variable (that cannot be varied, so, actually, yet another kind of constant) but not why one would do that. What problems do sigilless variables solve? What's a practical scenario when a variable, a constant, a binding variable do not solve the problem that a sigilless variable solve?
Constants are common in many languages. You don't want to write 3.14 all over your code and that's why you define a constant PI that, by the nature of the thing it represents, cannot be changed and that's why its value is constant in time.
For that you use constant.
A constant is only initialized once, when first encountered during compilation, and never again, so its value remains constant throughout a program run. The compiler can rely on that. So can the reader -- provided they clearly understand what "value" means in this context.1
For example:
BEGIN my $bar = 42;
loop {
constant foo = $bar;
$bar++;
last if $++ > 4;
print foo; # 4242424242
}
Note that if you didn't have the BEGIN then the constant would be initialized and be stuck with a value of (Any).
I tend to leave off the sigil to somewhat reinforce the idea it's a constant but you can use a sigil if you prefer.
Defining a variable bound to another entity with := is also almost clear but not really. If I bind a variable to 3.14, isn't it the same as defining a PI constant? Is $a := $b actually defining $a as an alias to $b or, as some other languages call it, a reference?
Binding just binds the identifiers. If either changes, they both do:
my $a = 42;
my $b := $a;
$a++;
$b++;
say $a, $b; # 4444
Finally, the documentation explains how one can define a sigilless variable (that cannot be varied, so, actually, another kind of constant) but not why one would do that.
It can be varied if the thing bound is a variable. For example:
my $variable = 42; # puts the value 42 INTO $variable
my \variable = $variable; # binds variable to $variable
say ++variable; # 43
my \variable2 = 42; # binds variable2 to 42
say ++variable2; # Cannot resolve caller prefix:<++>(Int:D);
# ... candidates ... require mutable arguments
I personally prefer to slash sigils if an identifier is bound to an immutable basic scalar value (eg 42) or other entirely immutable value (eg a typical List) and use a sigil otherwise. YMMV.
What problems do sigilless variables solve? What's a practical scenario when a variable, a constant, a binding variable do not solve the problem that a sigilless variable solve?
Please add comments if what's already in my answer (or another; I see someone else has posted one) leaves you with remaining questions.
Foonotes
1 See my answer to JJ's SO about use of constant with composite containers.
That's not one but at 5 questions?
If I bind a variable to 3.14, isn't it the same as defining a PI constant?
Well, technically it would be except for the fact that 3.14 is a Rat, and pi (aka π) is a Num.
Is $a := $b actually defining $a as an alias to $b or, as some other languages call it, a reference?
It's an alias.
$number_of_cakes = 4; $also_the_number_of_cakes := $number_of_cakes;
There would be little point. However, sometimes it can be handy to alias something to an element in an array, or a key in a hash, to prevent repeated lookups:
my %foo;
my $bar := %foo<bar>;
++$bar for ^10;
What problems do sigilless variables solve?
Sigilless variables only solve a programming style issue, as far as I know. Some people, for whatever reason, prefer sigilless varriables. It makes it harder to interpolate, but others might find the extra curlies actually a good thing:
my answer := my $ = 42;
say "The answer is {answer}";
What's a practical scenario when a variable, a constant, a binding variable do not solve the problem that a sigilless variable solve?
In my view, sigilless variables do not solve a problem, but rather try to cater different programming styles. So I'm not sure what a correct answer to this question would be.
Also constant is our scoped by default.

Changing $*DISTRO values for testing

I need to test a feature that includes this line:
if $translate-nl && $*DISTRO.is-win
I have tried to reassign a value to $*DISTRO,
$*DISTRO='Windows 10';
but it says:
Cannot modify an immutable Distro (debian (9.stretch))␤
$*DISTRO is a dynamic variable, and it makes sense that it's not modified. That said, is there any other way that code can be tested (other than going to Windows, of course)
my $*DISTRO = ...
Hopefully modifying the original is unnecessary. It's generally unreasonable action-at-a-distance -- almost certainly so if someone has arranged for it to be an immutable value. This is the reason why global variables got such a bad reputation.
To elaborate on raiph's answer: the * in $*DISTRO marks it as a dynamic variable. You can re-declare it any scope, and code called from there will see the redeclared value:
{
my $*DISTRO = ...;
# coded called from here sees the redeclared value
}
# code called from here sees the original value
Now, the question remains, what do you put in place of these pesky ...?
In the simplest case, a mock that just has whatever the code under test needs:
{
my class Distro { has $.is-win }
my $*DISTRO = Distro.new( :is-win );
# call your test code here
}
If the code needs more attributes in Distro, just add them to the mock Distro class.
If the code needs a "real* Distro object, for some reason, you can instantiate the built-in one. The constructor .new isn't really documented, but the source code makes it pretty obvious what arguments it expects.
OK, I got the answer relatively fast. $*DISTRO is actually a read-only alias of PROCESS::<$DISTRO>
So we only need to do:
my $*DISTRO = Distro.new(:is-win,:release<11>,:path-sep('|||'),:auth<unknown>,:name<mswin32>,:desc<Test>,:version<v11>);
say $*DISTRO.is-win; #OUTPUT: «True␤»

Variable Encapsulation in Case Statement

While modifying an existing program's CASE statement, I had to add a second block where some logic is repeated to set NetWeaver portal settings. This is done by setting values in a local variable, then assigning that variable to a Changing parameter. I copied over the code and did a Pretty Print, expecting to compiler to complain about the unknown variable. To my surprise however, this code actually compiles just fine:
CASE i_actionid.
WHEN 'DOMIGO'.
DATA: ls_portal_actions TYPE powl_follow_up_sty.
CLEAR ls_portal_actions.
ls_portal_actions-bo_system = 'SAP_ECC_Common'.
" [...]
c_portal_actions = ls_portal_actions.
WHEN 'EBELN'.
ls_portal_actions-bo_system = 'SAP_ECC_Common'.
" [...]
C_PORTAL_ACTIONS = ls_portal_actions.
ENDCASE.
As I have seen in every other programming language, the DATA: declaration in the first WHEN statement should be encapsulated and available only inside that switch block. Does SAP ignore this encapsulation to make that value available in the entire CASE statement? Is this documented anywhere?
Note that this code compiles just fine and double-clicking the local variable in the second switch takes me to the data declaration in the first. I have however not been able to test that this code executes properly as our testing environment is down.
In short you cannot do this. You will have the following scopes in an abap program within which to declare variables (from local to global):
Form routine: all variables between FORM and ENDFORM
Method: all variables between METHOD and ENDMETHOD
Class - all variables between CLASS and ENDCLASS but only in the CLASS DEFINITION section
Function module: all variables between FUNCTION and ENDFUNCTION
Program/global - anything not in one of the above is global in the current program including variables in PBO and PAI modules
Having the ability to define variables locally in a for loop or if is really useful but unfortunately not possible in ABAP. The closest you will come to publicly available documentation on this is on help.sap.com: Local Data in the Subroutine
As for the compile process do not assume that ABAP will optimize out any variables you do not use it won't, use the code inspector to find and remove them yourself. Since ABAP works the way it does I personally define all my variables at the start of a modularization unit and not inline with other code and have gone so far as to modify the pretty printer to move any inline definitions to the top of the current scope.
Your assumption that a CASE statement defines its own scope of variables in ABAP is simply wrong (and would be wrong for a number of other programming languages as well). It's a bad idea to litter your code with variable declarations because that makes it awfully hard to read and to maintain, but it is possible. The DATA statements - as well as many other declarative statements - are only evaluated at compile time and are completely ignored at runtime. You can find more information about the scopes in the online documentation.
The inline variable declarations are now possible with the newest version of SAP Netweaver. Here is the link to the documentation DATA - inline declaration. Here are also some guidelines of a good and bad usage of this new feature
Here is a quote from this site:
A declaration expression with the declaration operator DATA declares a variable var used as an operand in the current writer position. The declared variable is visible statically in the program from DATA(var) and is valid in the current context. The declaration is made when the program is compiled, regardless of whether the statement is actually executed.
Personally have not had time to check it out yet, because of lack of access to such system.