Why can't I call meta methods on Routine::WrapHandle? - raku

This is a continuing question from my previous one Why is Perl 6's unwrap method a method of Routine?, but mostly unrelated.
The wrap method is documented to return "an instance of a private class called WrapHandle. Besides that being odd for leaking a class that's private, it's not actually the name of the thing that comes back. The class is actually Routine::WrapHandle:
$ perl6
> sub f() { say 'f was called' }
sub f () { #`(Sub|140397740886648) ... }
> my $wrap-handle = &f.wrap({ say 'before'; callsame; say 'after' });
Routine::WrapHandle.new
But here's the question. I wanted to call .^methods on Routine::WrapHandle. That doesn't work:
> Routine::WrapHandle.^methods
Could not find symbol '&WrapHandle'
in block <unit> at <unknown file> line 1
This is the same as trying it on an undefined class name:
> Foo::Baz.^methods
Could not find symbol '&Baz'
in block <unit> at <unknown file> line 1
I can call meta methods on the instance though:
> $wrap-handle.^methods
(restore)
> $wrap-handle.^name
Routine::WrapHandle
What's going on there?

The definition of Routine::WrapHandle looks something like this:
my class Routine {
method wrap(&wrapper) {
my class WrapHandle { ... }
...
}
}
We can ignore the surrounding method; the important bit is that we're dealing with a lexical inner class defined within an outer class. Simplifying some more, we arrive at the following pattern:
package Foo {
my class Bar {}
say Bar.^name; #=> Foo::Bar
}
say try Foo::Bar; #=> Nil
The fully qualified name of the inner class will include the name of the enclosing package, but due to the explicit my (instead of the implicit our), the class will not be installed as a package variable and the lookup at file scope fails.

Related

Using public and private methods inside their class in Perl 6

If I have a public method, I can call it inside its class using both $.name and self.name:
class TEST {
has Int $.a;
method b($x) {
return $!a * $x;
}
method c($y) {
return self.b($y) * 3; # or $.b($y)
}
}
my $m = TEST.new(a => 10);
say $m.c(2); # 60
But if I make b a private method, I only can call it with self!b, not $!b, otherwise I get the following error message:
Attribute $!b not declared in class TEST
What's behind this rule? What are the rules of calling a method inside its own class?
An attribute can always be referred to as $!foo in a class. If you do that, than the code will be generated to directly access the attribute itself, and any classes subclassing your class will not be able to change this behaviour.
If you use has $.foo in the declaration of a class, it means that a public accessor (and if you add is rw it can also function as a mutator).
When you use $.foo in your code otherwise, it is exactly the same as $( self.foo ). This means that it will call the method foo on self, and itemize the return value (make it a single "thing" if it wasn't yet). This will go wrong if you defined your attribute with $!foo and you did not supply a method foo yourself.
This goes even further: $.bar really means self.bar: you only need to have a method existing by the name bar, which may not be related to any attribute at all.
If you define a private method !baz, the ! just indicates the privacy of the method, which means you need to call it indeed as self!baz. There is no short syntax for it.
Personally I dislike the fact that you can say $.zippo even if zippo is not an attribute. But I'm afraid that ship has sailed. But this behaviour is now causing you confusion :-(
So what's behind the rule for not having a short syntax for calling a private method? Not sure, I guess really that $!foo was already taken to mean direct access to the attribute, and provide you with a compile time error if the attribute doesn't exist.
Hope this answers your question!

Create Method via GDSL script that has a delegating closure parameter

Using the (scarcely documented) gdsl scripts of Intellij, one can add dynamic methods to a class:
contributor(context(ctype: "my.Type")) {
method name: "doIt", params: [body: {}], type: void
}
One can also configure the delegation of a closure:
contributor(context(scope: closureScope())) {
def call = enclosingCall("doIt")
if (call) {
def method = call.bind()
def clazz = method?.containingClass
if (clazz?.qualName == 'my.Type') {
delegatesTo(findClass('my.Inner'))
}
}
}
Which, when doIt is a method that is defined in the code (not dynamically added), also works as designed.
However, when using the closureScope with the previously created method, the containing class method is always null, meaning that I can not safely delegate inside the closure to the addressed my.Inner class.
What I want is adding a dynamic method equivalent to:
void doIt(#DelegatesTo(my.Inner) Closure)...
I.e. I want the method to be available in code completion (this works), and inside the so created closure, I want correct code completion when addressing methods of my.Inner.
So far, I tried various approaches:
include the #DelegatesTo annotation in the param definition
try more esoteric approaches in finding the owner of the closure, which fails because the GrMethodCall simply has no parent
unconditionally delegating all closures named doIt to my.Inner which works, but is no viable solution since I do have multiple doIt methods (on different classes) delegating to different targets.
So, how can I make IDEA behave as expected and delegate to the correct target?
Edit to make it clearer:
Given the following classes:
package my
class Type {
void doIt(Closure) {}
}
class Inner {
void inInner() {}
}
and the following gdsl:
contributor(context(scope: closureScope())) {
def call = enclosingCall("doIt")
if (call) {
def method = call.bind()
def clazz = method?.containingClass
println clazz?.qualName
if (clazz?.qualName == 'my.Type') {
delegatesTo(findClass('my.Inner'))
}
}
}
when I start typing in a new script:
new Type().doIt {
inInner()
}
When inside the closure, I get the following:
code completion for inInner
inInner is shown as valid
The console output when started with idea.bat from commandline shows the line my.Type (from the println)
Ctrl-B on inInner correctly links to source code.
(The same behaviour can be reached without the gdsl when annotation the Closure Parameter in the doIt method with #DelegatesTo(Inner))
However, I do not want to manually include the doIt method in the source of Type, it is generated by an AST Transformation, so my source file now looks like this:
package my
class Type {
}
class Inner {
void inInner() {}
}
I can tell IntelliJ about the new method using the following gdsl snippet
contributor(context(ctype: "my.Type")) {
method name: "doIt", params: [body: {}], type: void
}
Now the IDE correctly recognizes the doIt method with a closure parameter. However, inside the Closure, the following happens:
sometimes code completion shows inInner, sometimes after changing something, it does not (when using the original code to fix a type, it was shown, but later declared "unresolved", after going through the code changes of this edited example, it is not shown anymore...)
Even when shown, inInner is shown with "cannot resolve symbol" decoration
the console shows null as clazz, i.e. the method is found, but not linked to an owner ASTNode
Ctrl-B does not link to the corresponding method in Inner
So what I want is the same behaviour for an injected doIt method (via Gdsl) as with a method included in the source, i.e. I want the gdsl to inject a doIt method with a delegating closure (to Inner) into the type class.
This worked for me adding the ctype to scope insted of finding the class type from the method
contributor(context(scope: closureScope(), ctype: 'my.Type')) {
def call = enclosingCall("doIt")
if (call) {
delegatesTo(findClass('my.Inner'))
}
}

Moose::Role weirdness with overridden methods

Base.pm:
package Base;
use Moose::Role;
sub f {
my ($self) = #_;
print "In role.\n";
}
1;
X.pm:
package X;
use Moose;
with 'Base';
around 'f' => sub {
my ($next, $self) = #_;
print "Nevermind, we are in class X.\n";
};
__PACKAGE__->meta->make_immutable;
1;
Y.pm:
package Y;
use Moose;
with 'Base';
override 'f' => sub {
my ($self) = #_;
print "Nevermind, we are in class Y.\n";
};
__PACKAGE__->meta->make_immutable;
1;
Then X does work and Y does not. It is a weird design, as override is just a special case of around and as a special case should work also.
Can anyone explain why this design decision and why it is so weird?
$ perl X.pm
$ perl Y.pm
Cannot add an override method if a local method is already present at /usr/lib/i386-linux-gnu/perl5/5.22/Moose/Exporter.pm line 419
Moose::override('f', 'CODE(0x9c002f8)') called at Y.pm line 9
The documentation describes override as:
An override method is a way of explicitly saying "I am overriding this method from my superclass". You can call super within this method, and it will work as expected. The same thing can be accomplished with a normal method call and the SUPER:: pseudo-package; it is really your choice.
What you are doing contradicts this definition. With a role, f is installed in your package. You are attempting to define another f in the same package.
The fact that your role is called Base indicates to me that you have some confusion when it comes to the difference between inheritance versus composition.
The purpose of around is to wrap a method in the current class regardless of whether it was implemented in the same package or inherited or composed:
Method modifiers can be used to add behavior to methods without modifying the definition of those methods.
Just a straightforward reading of these two snippets makes the distinction clear to me.
When you apply the role that defines f, that itself overrides any inherited method f. If you then say override 'f', you are declaring your intention to override f again. Well, there can only be one method f in one class. Which one should count? The one that you get by applying the role, or the one you just defined? For all intents and purposes, the methods you get from composing a role are just like methods you defined manually in the class. There is no reason a priori one should be more important than the other.
Think of this as airline travel. Think of the method modifiers as classes: First, business, economy etc. So, in first class, get_dinner_menu maybe wrapped with appetizers, sweets, desserts etc.
On the other hand, override is like changing the flight. It's as if you are saying "I want to fly on both TK 1 and UIA 213". Makes no sense.
Maybe the following script will make things a bit clearer by showing a naive implementation of around and overriding a method without using override.
#!/usr/bin/env perl
use strict;
use warnings;
{
package MyRole;
use Moose::Role;
sub f {
print "in role\n";
}
}
{
package X;
use Moose;
with 'MyRole';
around 'f' => sub {
my ($orig, $self) = #_;
print "In wrapper\n";
return $self->$orig( #_ );
};
{
my $f = \&f;
{
no warnings 'redefine';
*f = sub {
my ($self) = #_;
print "In wrapper wrapper\n";
return $self->$f( #_ );
}
}
}
}
{
package Y;
use Moose;
with 'MyRole';
sub f {
print "In overridden method\n";
}
}
print '=-=' x 22, "\n";
my $x = X->new;
$x->f;
print '=-=' x 22, "\n";
my $y = Y->new;
$y->f;
Output:
=-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-=
In wrapper wrapper
In wrapper
in role
=-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-=
In overridden method

What does the keyword 'unit' before a package name do?

In the following code;
unit module Fancy::Calculator;
what does 'unit' actually do? I know that the scope for the definition of the module becomes the file its declared in - as opposed to;
module Fancy::Calculator {
# module definition statements here
}
where the scope is obviously defined by the curlies but I can't see anything in the documentation that definitively states that that is all that it does and I'd be a little surprised if that's all that it did. Secondarily, after making such a declaration, can one declare unit class Whatever (class, module, whatever) half way down and call an end to the previous scope definition?
From a commenter (thanks Brad), it appears that is all it does. As for starting a second Module within the same file - you can't use unit module again - that produces;
===SORRY!=== Error while compiling /home/user/Fancy/Calculator.pm6
Too late for unit-scoped module definition;
Please use the block form.
...but as the message says, you can use the block form but whatever you declare is within the unit module namespace - Fancy::Calculator in this case. So these;
unit module Fancy::Calculator;
# The following available to the module user as Fancy::Calculator::Adder
class Adder {
method add { "Hi... I am a method who can add" }
}
# Starting definition of new module but its within Fancy::Calculator namespace
module Minus {
# Following available to the module user as Fancy::Calculator::Minus::Subber
class Subber {
method subtract { "Hi... I am a method who can subtract" }
}
# unless you add "is export" in which case its available by its short name
class Multiplyer is export {
method times { "Hi... I am a method who can multiply" }
}
sub divide() is export { "Hi... I am a sub who can divide" }
}
are accessed like this;
# In main
use Fancy::Calculator;
my $fca = Fancy::Calculator::Adder.new;
say $fca.add; # Hi... I am a method who can add
my $fcms = Fancy::Calculator::Minus::Subber.new;
say $fcms.subtract; # Hi... I am a method who can subtract
my $mul = Multiplyer.new;
say $mul.times; # Hi... I am a sub who can multiply
say divide(); # Hi... I am a sub who can divide

A constructor with only 1 argument in Perl 6

I want to override new so that my class can be created only by passing one argument to the constructor, no more and no fewer.
class MyClass {
has $.var1;
method new($var1) {
return MyClass.new(var1 => $var1);
}
}
my $my_class1 = MyClass.new(33);
say $my_class1.var1;
The error is:
Too few positionals passed; expected 2 arguments but got 1
in method new at test1.pl6:28
in method new at test1.pl6:28
in block <unit> at test1.pl6:33
What's up with it?
Custom constructors need to call bless, ie
class MyClass {
has $.var1;
method new($var1) {
return self.bless(var1 => $var1);
}
}
There are a few things that can be improved, eg
one could add an explicit invocant parameter and use :U to make .new() fail when called on instance objects
the explicit return is superfluous - the last expression within the method will be returned anyway, and currently, it actually hurts performance
there's syntactic sugar for passing a named argument held in a variable of the same name
Putting it all together, we end up with
class MyClass {
has $.var1;
method new(MyClass:U: $var1) {
self.bless(:$var1);
}
}
As to where your error comes from:
Your method new is declared to take a positional argument (giving a total count of 2 expected arguments due to the implicit invocant), but the call MyClass.new(var1 => $var1) only passed a named one. Note that said method is the only .new() present in your class, so if the call had actually worked, you would have ended up with infinite recursion!