Get method called-as str in the callee - oop

I would like to introspect the tail end of a method call from the callee side.
Right now I am doing this explicitly...
# caller side
s.pd: '.shape';
s.pd: '.to_json("test.json")';
s.pd: '.iloc[2] = 23';
# callee side
method pd( Str $call ) {
given $call {
when /shape/ { ... }
when /to_json/ { ... }
#etc
}
}
BUT, I would like to do this by way of a 'slang method call', something like this made up code...
# caller side
s.pd.shape;
s.pd.to_json("test.json");
s.pd.iloc[2] = 23;
^ ^^ ^^^^^^^^^^^^^^^^^^^$
| | |
| | L a q// str I can put into eg. a custom Grammar
| |
| L general method name
|
L invocant
#callee side
method pd( ?? ) {
my $called-as-str = self.^was-called-as;
say $called-as-str; #'pd.to_json("test.json")'
...
}
(HOW) can this be done in raku?
Since there are 422 calls to handle, of many arity patterns, answers that require the declaration of 422 methods and signatures in the called Class will be less attractive.

Per #jonathans comment, the raku docs state:
A method with the special name FALLBACK will be called when other
means to resolve the name produce no result. The first argument holds
the name and all following arguments are forwarded from the original
call. Multi methods and sub-signatures are supported.
class Magic {
method FALLBACK ($name, |c(Int, Str)) {
put "$name called with parameters {c.raku}" }
};
Magic.new.simsalabim(42, "answer");
# OUTPUT: «simsalabim called with parameters ⌈\(42, "answer")⌋␤»
So my code example would read:
# callee side
class Pd-Stub {
method FALLBACK ($name, Str $arg-str ) {
say "$name called with args: <<$arg-str>>"
}
}
class Series {
has Pd-Stub $.pd
}
my \s = Series.new;
# caller side
s.pd.shape; #shape called with args: <<>>
s.pd.to_json("test.json"); #to_json called with args: <<test.json>>
s.pd.iloc[2] = 23; #iloc called with args: <<>>
#iloc needs to use AT-POS and Proxy to handle subscript and assignment

Related

Raku Ambiguous call to infix(Hyper: Dan::Series, Int)

I am writing a model Series class (kinda like the one in pandas) - and it should be both Positional and Associative.
class Series does Positional does Iterable does Associative {
has Array $.data is required;
has Array $.index;
### Construction ###
method TWEAK {
# sort out data-index dependencies
$!index = gather {
my $i = 0;
for |$!data -> $d {
take ( $!index[$i++] => $d )
}
}.Array
}
### Output ###
method Str {
$!index
}
### Role Support ###
# Positional role support
# viz. https://docs.raku.org/type/Positional
method of {
Mu
}
method elems {
$!data.elems
}
method AT-POS( $p ) {
$!data[$p]
}
method EXISTS-POS( $p ) {
0 <= $p < $!data.elems ?? True !! False
}
# Iterable role support
# viz. https://docs.raku.org/type/Iterable
method iterator {
$!data.iterator
}
method flat {
$!data.flat
}
method lazy {
$!data.lazy
}
method hyper {
$!data.hyper
}
# Associative role support
# viz. https://docs.raku.org/type/Associative
method keyof {
Str(Any)
}
method AT-KEY( $k ) {
for |$!index -> $p {
return $p.value if $p.key ~~ $k
}
}
method EXISTS-KEY( $k ) {
for |$!index -> $p {
return True if $p.key ~~ $k
}
}
#`[ solution attempt #1 does NOT get called
multi method infix(Hyper: Series, Int) is default {
die "I was called"
}
#]
}
my $s = Series.new(data => [rand xx 5], index => [<a b c d e>]);
say ~$s;
say $s[2];
say $s<b>;
So far pretty darn cool.
I can go dd $s.hyper and get this
HyperSeq.new(configuration => HyperConfiguration.new(batch => 64, degree => 1))
BUT (there had to be a but coming), I want to be able to do hyper math on my Series' elements, something like:
say $s >>+>> 2;
But that yields:
Ambiguous call to 'infix(Hyper: Dan::Series, Int)'; these signatures all match:
(Hyper: Associative:D \left, \right, *%_)
(Hyper: Positional:D \left, \right, *%_)
in block <unit> at ./synopsis-dan.raku line 63
How can I tell my class Series not to offer the Associative hyper candidate...?
Note: edited example to be a runnable MRE per #raiph's comment ... I have thus left in the minimum requirements for the 3 roles in play per docs.raku.org
After some experimentation (and new directions to consider from the very helpful comments to this SO along the way), I think I have found a solution:
drop the does Associative role from the class declaration like this:
class Series does Positional does Iterable {...}
BUT
leave the Associative role support methods in the body of the class:
# Associative role support
# viz. https://docs.raku.org/type/Associative
method keyof {
Str(Any)
}
method AT-KEY( $k ) {
for |$!index -> $p {
return $p.value if $p.key ~~ $k
}
}
method EXISTS-KEY( $k ) {
for |$!index -> $p {
return True if $p.key ~~ $k
}
}
This gives me the Positional and Associative accessors, and functional hyper math operators:
my $s = Series.new(data => [rand xx 5], index => [<a b c d e>]);
say ~$s; #([a => 0.6137271559776396 b => 0.7942959887386045 c => 0.5768018697817604 d => 0.8964323560788711 e => 0.025740150933493577] , dtype: Num)
say $s[2]; #0.7942959887386045
say $s<b>; #0.5768018697817604
say $s >>+>> 2; #(2.6137271559776396 2.7942959887386047 2.5768018697817605 2.896432356078871 2.0257401509334936)
While this feels a bit thin (and probably lacks the full set of Associative functions) I am fairly confident that the basic methods will give me slimmed down access like a hash from a key capability that I seek. And it no longer creates the ambiguous call.
This solution may be cheating a bit in that I know the level of compromise that I will accept ;-).
Take #1
First, an MRE with an emphasis on the M1:
class foo does Positional does Associative { method of {} }
sub infix:<baz> (\l,\r) { say 'baz' }
foo.new >>baz>> 42;
yields:
Ambiguous call to 'infix(Hyper: foo, Int)'; these signatures all match:
(Hyper: Associative:D \left, \right, *%_)
(Hyper: Positional:D \left, \right, *%_)
in block <unit> at ./synopsis-dan.raku line 63
The error message shows it's A) a call to a method named infix with an invocant matching Hyper, and B) there are two methods that potentially match that call.
Given that there's no class Hyper in your MRE, these methods and the Hyper class must be either built-ins or internal details that are leaking out.
A search of the doc finds no such class. So Hyper is undocumented Given that the doc has fairly broad coverage these days, this suggests Hyper is an internal detail. But regardless, it looks like you can't solve your problem using official/documented features.
Hopefully this bad news is still better than none.2
Take #2
Where's the fun in letting little details like "not an official feature" stop us doing what we want to do?
There's a core.c module named Hyper.pm6 in the Rakudo source repo.
A few seconds browsing that, and clicks on its History and Blame, and I can instantly see it really is time for me to conclude this SO answer, with a recommendation for your next move.
To wit, I suggest you start another SO, using this answer as its heart (but reversing my presentation order, ie starting by mentioning Hyper, and that it's not doc'd), and namechecking Liz (per Hyper's History/Blame), with a link back to your Q here as its background. I'm pretty sure that will get you a good answer, or at least an authoritative one.
Footnotes
1 I also tried this:
class foo does Positional does Associative { method of {} }
sub postfix:<bar>(\arg) { say 'bar' }
foo.new>>bar;
but that worked (displayed bar).
2 If you didn't get to my Take #1 conclusion yourself, perhaps that was was because your MRE wasn't very M? If you did arrive at the same point (cf "solution attempt #1 does NOT get called" in your MRE) then please read and, for future SOs, take to heart, the wisdom of "Explain ... any difficulties that have prevented you from solving it yourself".

Object, roles and multiple dispatch

I'm trying to use multiple dispatch to overload and use methods within composed classes. Here's the implementation:
role A {
has $!b;
submethod BUILD( :$!b ) {}
multi method bar () {
return $!b;
}
}
class B does A {
submethod BUILD( :$!b ) {}
multi method bar() {
return " * " ~ callsame ~ " * ";
}
}
my $a = A.new( b => 33);
say $a.bar();
my $b = B.new( b => 33 );
say $b.bar();
This fails, however, with:
Calling callsame(Str) will never work with declared signature ()
(I really have no idea why callsame uses Str as a signature). Changing the method bar to use callwith:
multi method bar() {
return " * " ~ callwith() ~ " * ";
}
Simply does not work:
Use of Nil in string context
in method bar at multi.p6 line 18
* *
Is there any special way to work with call* within roles/classes?
The first issue is a matter of syntax. A listop function call parses an argument list after it, starting with a term, so this:
return " * " ~ callsame ~ " * ";
Groups like this:
return " * " ~ callsame(~ " * ");
And so you're calling the ~ prefix operator on " * ", which is where the Str argument it complains about comes from.
Ultimately, however, the issue here is a misunderstanding of the semantics of role composition and/or deferral. Consider a non-multi case:
role R { method m() { say 1; callsame() } }
class B { method m() { say 2; callsame() } }
class C is B does R { method m() { say 3; callsame(); } }
C.m
This outputs:
3
2
Notice how 1 is never reached. This is because role composition is flattening: it's as if the code from the role were put into the class. When the class already has a method of that name, then it is taken in favor of the one in the role.
If we put multi on each of them:
role R { multi method m() { say 1; callsame() } }
class B { multi method m() { say 2; callsame() } }
class C is B does R { multi method m() { say 3; callsame(); } }
C.m
The behavior is preserved:
3
2
Because the role composer accounts for the long name of the multi method - that is, accounting for the signature. Since they are the very same, then the one in the class wins. Were it to retain both, we'd end up with the initial call resulting in an ambiguous dispatch error!
Deferral with nextsame, callsame, nextwith, and callwith all iterate through the possible things we could have dispatched to.
In the case of a non-multi method, that is achieved by walking the MRO; since the method from the role was not composed, then it doesn't appear in any class in the MRO (nothing that only classes appear in the MRO, since roles are flattened away at composition time).
In the case of a multi method, we instead walk the set of candidates that would have accepted the arguments of the initial dispatch. Again, since the identically long-named method in the class was chosen in favor of the role one at composition time, the one from the role simply isn't in consideration for the dispatch in the first place: it isn't in the candidate list of the proto, and so won't be deferred to.

Binding of private attributes: nqp::bindattr vs :=

I'm trying to find how the binding operation works on attributes and what makes it so different from nqp::bindattr. Consider the following example:
class Foo {
has #!foo;
submethod TWEAK {
my $fval = [<a b c>];
use nqp;
nqp::bindattr( nqp::decont(self), $?CLASS, '#!foo',
##!foo :=
Proxy.new(
FETCH => -> $ { $fval },
STORE => -> $, $v { $fval = $v }
)
);
}
method check {
say #!foo.perl;
}
}
my $inst = Foo.new;
$inst.check;
It prints:
$["a", "b", "c"]
Replacing nqp::bindattr with the binding operator from the comment gives correct output:
["a", "b", "c"]
Similarly, if foo is a public attribute and accessor is used the output would be correct too due to deconterisation taking place within the accessor.
I use similar code in my AttrX::Mooish module where use of := would overcomplicate the implementation. So far, nqp::bindattr did the good job for me until the above problem arised.
I tried tracing down Rakudo's internals looking for := implementation but without any success so far. I would ask here either for an advise as to how to simulate the operator or where in the source to look for its implementation.
Before I dig into the answer: most things in this post are implementation-defined, and the implementation is free to define them differently in the future.
To find out what something (naively) compiles into under Rakudo Perl 6, use the --target=ast option (perl6 --target=ast foo.p6). For example, the bind in:
class C {
has $!a;
submethod BUILD() {
my $x = [1,2,3];
$!a := $x
}
}
Comes out as:
- QAST::Op(bind) :statement_id<7>
- QAST::Var(attribute $!a) <wanted> $!a
- QAST::Var(lexical self)
- QAST::WVal(C)
- QAST::Var(lexical $x) $x
While switching it for #!a like here:
class C {
has #!a;
submethod BUILD() {
my $x = [1,2,3];
#!a := $x
}
}
Comes out as:
- QAST::Op(bind) :statement_id<7>
- QAST::Var(attribute #!a) <wanted> #!a
- QAST::Var(lexical self)
- QAST::WVal(C)
- QAST::Op(p6bindassert)
- QAST::Op(decont)
- QAST::Var(lexical $x) $x
- QAST::WVal(Positional)
The decont instruction is the big difference here, and it will take the contents of the Proxy by calling its FETCH, thus why the containerization is gone. Thus, you can replicate the behavior by inserting nqp::decont around the Proxy, although that rather begs the question of what the Proxy is doing there if the correct answer is obtained without it!
Both := and = are compiled using case analysis (namely, by looking at what is on the left hand side). := only works for a limited range of simple expressions on the left; it is a decidedly low-level operator. By contrast, = falls back to a sub call if the case analysis doesn't come up with a more efficient form to emit, though in most cases it manages something better.
The case analysis for := inserts a decont when the target is a lexical or attribute with sigil # or %, since - at a Perl 6 level - having an item bound to an # or % makes no sense. Using nqp::bindattr is going a level below Perl 6 semantics, and so it's possible to end up with the Proxy bound directly there using that. However, it also violates expectations elsewhere. Don't expect that to go well (but it seems you don't want to do that anyway.)

Singleton implementation on perl6

I looked at the following code on Rosetta code http://rosettacode.org/wiki/Singleton#Perl_6
which implements Singleton in Perl6
class Singleton {
has Int $.x is rw;
# We create a lexical variable in the class block that holds our single instance.
my Singleton $instance = Singleton.bless; # You can add initialization arguments here.
method new {!!!} # Singleton.new dies.
method instance { $instance; }
}
my $a=Singleton.bless(x => 1);
my $b=Singleton.bless(x=> 2);
say $a.x;
say $b.x;
#result
# 1
# 2
but it seems using this implementation i can create tow instances of the same class using bless see example above ,
is there an option to prevent the implemention to only one instance of the same class ?
Perl prides itself on providing many ways to do things leaving you to pick the one that suits your tastes and the application at hand. I say that to highlight that this is one simple but solid, hopefully self-explanatory way - I'm not putting it forward as "the best" because that depends on your circumstances.
#!/usr/bin/env perl6
class Scoreboard {
has Str $.home-team ;
has Str $.away-team ;
has Int $.home-score = 0 ;
has Int $.away-score = 0 ;
my Scoreboard $instance;
method new(*%named) {
return $instance //= self.bless(|%named) ;
}
multi method goal($team where * eq $!home-team, Int :$points = 6) {
$!home-score += $points
}
multi method goal($team where * eq $!away-team, Int :$points = 6) {
$!away-score += $points
}
method Str {
"At this vital stage of the game " ~
do given $!home-score <=> $!away-score {
when More {
"$!home-team are leading $!away-team, $!home-score points to $!away-score"
}
when Less {
"$!home-team are behind $!away-team, $!home-score points to $!away-score"
}
default {
"the scores are level! $!home-score apeice!"
}
}
}
}
my $home-team = "The Rabid Rabbits";
my $away-team = "The Turquoise Turtles"; # Go them turtles!
my $scoreboard = Scoreboard.new( :$home-team , :$away-team );
$scoreboard.goal($home-team, :4points) ;
say "$scoreboard";
$scoreboard.goal($away-team) ;
say "$scoreboard";
my $evil_second_scoreboard = Scoreboard.new;
$evil_second_scoreboard.goal($home-team, :2points) ;
say "$evil_second_scoreboard";
This produces;
At this vital stage of the game The Rabid Rabbits are leading The Turquoise Turtles, 4 points to 0
At this vital stage of the game The Rabid Rabbits are behind The Turquoise Turtles, 4 points to 6
At this vital stage of the game the scores are level! 6 apeice!
This overrides the default new (normally supplied by class Mu) and keep a reference to ourself (ie this object) in private class data. For private class data, we use a lexically scoped scalar declared with my. The // is the operator form of .defined. So, on the first run, we call bless which allocates the object and initialize the attributes, and then assign it to $instance. In subsequent calls to new, $instance is defined and is immediately returned.
If you want to prevent someone calling bless directly, you can add;
method bless(|) {
nextsame unless $instance;
fail "bless called on singleton Scoreboard"
}
which will ensure that only the first call will work.

Customize 'at' with extra parameters for the closure?

Is it possible to specify some optional parameter(s) to the 'at' closure on the page like this:
class ManagerDashboardClientsPage extends Page {
static at = { year, geo ->
if (year) {
GebUtil.selectedYear == year
}
title.endsWith('Dashboard: Clients')
}
}
so that I can write both
at ManagerDashboardClientsPage
and
at ManagerDashboardClientsPage(2013, 'North East')
Currently the first one breaks with
No signature of method: page.ManagerDashboardClientsPage$__clinit__closure1.doCall() is applicable for argument types: () values: []
Possible solutions: doCall(java.lang.Object, java.lang.Object), call(), call([Ljava.lang.Object;), call(java.lang.Object), call(java.lang.Object, java.lang.Object), equals(java.lang.Object)
groovy.lang.MissingMethodException: No signature of method: page.ManagerDashboardClientsPage$__clinit__closure1.doCall() is applicable for argument types: () values: []
Possible solutions: doCall(java.lang.Object, java.lang.Object), call(), call([Ljava.lang.Object;), call(java.lang.Object), call(java.lang.Object, java.lang.Object), equals(java.lang.Object)
at geb.Page.verifyThisPageAtOnly(Page.groovy:165)
at geb.Page.verifyAt(Page.groovy:133)
at geb.Browser.doAt(Browser.groovy:358)
at geb.Browser.at(Browser.groovy:289)
at geb.spock.GebSpec.methodMissing(GebSpec.groovy:51)
at spec.ManagerDashboardClientsSpec.login as CEO(ManagerDashboardClientsSpec.groovy:16)
In Groovy you can set default values for optional closure parameters, like so:
static at = { year=null, geo=null ->
...
}
I think that'll clear ya up. :)
update
Ok, I know you don't need it anymore, but I made this for my own use when I was learning Groovy, and I thought someone might find it helpful:
{ -> ... } a closure with exactly zero parameters. Groovy will blow up if you call it with params.
{ ... } a closure with one optional parameter, named "it"
{ foo -> ... } a closure with one parameter named "foo" (foo can be any type)
{ foo, bar, baz -> ... } a closure with 3 parameters named "foo", "bar" and "baz"
{ String foo -> ... } You can specify the type of the parameters if you like