How does one access a derived classes member variables from within the superclass in Tcl? - oop

Code:
package require TclOO
oo::class create Supe {
variable cape boots
constructor {} {
puts -nonewline "Supe: "
puts [info class variables [self class]]
}
}
oo::class create Clark {
superclass Supe
variable glasses suit
constructor {} {
puts -nonewline "Clark: "
puts [info class variables [self class]]
next
}
}
set hero [Clark new]
Output:
Clark: glasses suit
Supe: cape boots
Is it possible to get a list of Clark's member variables from within Supe's constructor without passing them into Supe as an argument?
Ultimately, the goal is to dynamically set derived class variables from a dict argument:
foreach {varName} [info class variables [self class]] {
variable $varName [dict get $args $varName]
}
If the above code can be used in the superclass constructor, it would avoid putting it in each derived class constructor.

You can get the name of the object with self object, or just self. You can then get the class of the object with info object class. And finally, you can get the member variables of a class with info class variables.
Putting it all together results in:
[info class variables [info object class [self]]]

My take does not add to the answer already given, but looks at the motivating problem of the OP:
Ultimately, the goal is to dynamically set derived class variables
from a dict argument:
What I have been using in the past to batch-update an object's state is sth. along the lines of:
oo::class create C {
variable a b
constructor {aDict} {
my variable {*}[lsort -unique [my lookupVars [info object class [self]]]]
# dict with aDict {;}
lassign [dict values $aDict] {*}[dict keys $aDict]
}
method lookupVars {currentClass} {
set v [info class variables $currentClass]
foreach scl [info class superclasses $currentClass] {
if {$scl eq "::oo::object"} {
break
}
lappend v {*}[my lookupVars $scl]
}
return $v
}
method print {} {
foreach v [info object vars [self]] {
my variable $v
puts "<$v> [set $v]"
}
}
}
The key items are:
lookupVars is a naturally recursive implementation walking the class, direct, and indirect superclasses to collect of defined per-class variables. This follows up on what Donal describes as a discovery of "properties". Note that this is limited in several ways (e.g., mixins are ignored, also TclOO's internal linearisation scheme is not reflected, no control for duplicates etc.)
lassign can be used to set multiple variables at once. Alternatively, but with some side effects, dict with "loads" a given dict's content into variables available for the current scope. my variable ?varName ...? will provide method-local links to the collected per-class variables. This way, you save a script-level loop and don't have to filter the provided dict for unmatched keys.
Watch:
oo::class create D {
superclass C
variable c d
}
oo::class create E {
superclass D
variable e f
}
[D new {a 1 b 2 c 3 d 4}] print
[E new {a 1 b 2 c 3 d 4 f 5 e 8 x 3}] print
[D new {a 1 b 2 c 3 d 4 f 5 x 3}] print

Related

Get method called-as str in the callee

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

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.

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.

Convert Namespace to a single proc

I have a set of procs and a namespace as you can see below:
namespace eval ::_API {
if {[info exists ::_API::API_ids]} {
catch {API erase -ids [array names ::_API::API_ids]}
}
catch {unset API_ids}
array set API_ids ""
}
proc ::_API::erase { } {
foreach id [array names ::_API::API_ids] {
if {::_API::API_ids($id) == 0} {
continue
}
if {[catch {API -id $id -redraw 0}] != 0} {
set ::_API::API_ids($id) 0
}
}
Redraw ;# I'm not concerned about this part
# and I'm fairly certain it can be ignored
}
proc erase { } {
::_API ::erase
}
::_API::API_ids is an array that contains points (e.g. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15). What the script does is erase points in a table.
I want to convert the namespace ::_API into a proc so that I can use a GUI button to call the proc. It is currently directly after some other scripts (which map the points in the table) and I want to erase them only when required; i.e. when the button will be pressed.
I already tried running ::_API::erase directly but it is not working:
proc ::_API::erase { } {
foreach id [array names ::_API::API_ids] {
if {::_API::API_ids($id) == 0} {
continue
}
if {[catch {API -id $id -redraw 0}] != 0} {
set ::_API::API_ids($id) 0
}
}
Redraw
}
I think that there might be something I'm missing about the namespace. I tried reading the documentation but I don't quite understand really how they work.
The first thing that you really must do is use variable to declare the variable. For some fairly ugly reasons, failing to do that can cause “fun” with variable resolution to make things happen in ways you don't expect:
namespace eval ::_API {
variable API_ids; ##### <<<<<<< THIS <<<<<<< #####
if {[info exists ::_API::API_ids]} {
catch {API erase -ids [array names ::_API::API_ids]}
}
catch {unset API_ids}
array set API_ids ""
}
Secondly, you probably ought to actually think in terms of using real OO for this rather than trying to fake it. For example, with TclOO you'd be writing something like:
oo::class create APIClass {
variable ids
constructor {} {
array set ids {}
}
method erase {} {
foreach id [array names ids] {
if {$ids($id) == 0} continue
if {[catch {
API -id $id -redraw 0
}]} {
set ids($id) 0
}
}
Redraw
}
# Allow something to reference the ids variable from the outside world
method reference {} {
return [my varname ids]
}
}
APIClass create _API
# [_API erase] will call the erase method on the _API object
This simplifies things quite a bit, and in fact you can think in terms of coupling the drawing and the data management quite a lot closer than I've done above; it's just indicative of what you can do. (I find that it makes stuff a lot simpler when I use objects, as they've got a much stronger sense of lifecycle about them than ordinary namespaces.)
What you mean is you want to convert the namespace initialization code into a procedure. The following example should achieve that.
namespace eval ::_API {
}
proc ::_API::initialize {} {
variable API_ids
if {[info exists API_ids]} {
catch {API erase -ids [array names API_ids]}
unset API_ids
}
array set API_ids ""
}
... more definitions ...
::_API::initialize
We start by declaring the namespace. Then replicate the original code in a procedure. As there is no point unsetting a non-existent variable, we move unset into the block that only runs if the variable exists.
At the end of the namespace definitions, initialize the namespace by calling its initialization function.

Evaluating a "variable variable"

I'm creating a dynamic variable ("Variable variable" in PHP parlance) with the following:
foo: "test1"
set to-word (rejoin [foo "_result_data"]) array 5
But how do I get the value of the resulting variable named "test1_result_data" dynamically? I tried the following:
probe to-word (rejoin [foo "_result_data"])
but it simply returns "test1_result_data".
As your example code is REBOL 2, you can use GET to obtain the value of the word:
>> get to-word (rejoin [foo "_result_data"])
== [none none none none none]
REBOL 3 handles contexts differently from REBOL 2. So when creating a new word you will need to handle it's context explicitly otherwise it will not have a context and you'll get an error when you try to set it. This is in contrast to REBOL 2 which set the word's context by default.
So you could consider using REBOL 3 code like the following to SET/GET your dynamic variables:
; An object, providing the context for the new variables.
obj: object []
; Name the new variable.
foo: "test1"
var: to-word (rejoin [foo "_result_data"])
; Add a new word to the object, with the same name as the variable.
append obj :var
; Get the word from the object (it is bound to it's context)
bound-var: in obj :var
; You can now set it
set :bound-var now
; And get it.
print ["Value of " :var " is " mold get :bound-var]
; And get a list of your dynamic variables.
print ["My variables:" mold words-of obj]
; Show the object.
?? obj
Running this as a script yields:
Value of test1_result_data is 23-Aug-2013/16:34:43+10:00
My variables: [test1_result_data]
obj: make object! [
test1_result_data: 23-Aug-2013/16:34:43+10:00
]
An alternative to using IN above could have been to use BIND:
bound-var: bind :var obj
In Rebol 3 binding is different than Rebol 2 and there are some different options:
The clumsiest option is using load:
foo: "test1"
set load (rejoin [foo "_result_data"]) array 5
do (rejoin [foo "_result_data"])
There is a function that load uses--intern--which can be used to bind and retrieve the word to and from a consistent context:
foo: "test1"
set intern to word! (rejoin [foo "_result_data"]) array 5
get intern to word! (rejoin [foo "_result_data"])
Otherwise to word! creates an unbound word that is not easy to utilize.
The third option is to use bind/new to bind the word to a context
foo: "test1"
m: bind/new to word! (rejoin [foo "_result_data"]) system/contexts/user
set m array 5
get m
probe do (rejoin [foo "_result_data"])
from http://www.rebol.com/docs/core23/rebolcore-4.html#section-4.6