Magic for object constructor wanted

K

Koszalek Opalek

My code creates new objects and then populates them with
data, like this:

my $joe = Dude->new();
my $tom = Dude->new();
my $ann = Dude->new();
my $jane = Dude->new();

$joe->fill(
name => "joe",
friends => [ $ann, $tom ]
);

$jane->fill(
name => "jane",
friends => [ $ann ]
);

You will notice that the name field is always the same as
the variable that stores the object. What kind of magic
would I have to use in the constructor (or in the fill()
method) so that the name of the object does not have
to be specified explicitly? In other words, I would like to
be able to say:

$joe->fill(
friends => [ $ann, tom ]
);

$jane->fill(
friends => [ $ann ]
);

and still have the name field set. This saves typing and
makes sure the variable name and the name field match.

Is this possible?

Koszalek
 
K

Koszalek Opalek

(...)
the name of the object does not have
to be specified explicitly? In other words, I would like to
be able to say:

$joe->fill(
friends => [ $ann, tom ]
);

$jane->fill(
friends => [ $ann ]
);

and still have the name field set. This saves typing and
makes sure the variable name and the name field match.


The following just occurred to me. It would be cool...
if it worked :) I have created a @Dudes array and then
iterate over the array creating objects dynamically
with eval() and passing whatever I need to the constructor.

The code errors out with
Can't call method "friends" on an undefined value at q.pl line 35.

Is there a way to declare variables with eval() in the
calling scope?

#!/usr/bin/perl
use strict;
no strict "vars";
use warnings;


{
package Dude;

sub new
{
my $class = shift;
my $self = {@_};
print "new() executed\n";
bless $self, $class;
};

sub friends
{
print "hello from $_[0]->{name}\n";
}
}


my @Dudes = ("joe", "tim");

for (@Dudes) {
my $code = qq{my \$$_ = Dude->new( "name" => "$_"); \$joe-
friends();};
print "$code\n";
eval $code;
};

$joe->friends();


__END__

Koszalek
 
M

Martijn Lievaart

My code creates new objects and then populates them with data, like
this:

my $joe = Dude->new();
my $tom = Dude->new();
my $ann = Dude->new();
my $jane = Dude->new();

$joe->fill(
name => "joe",
friends => [ $ann, $tom ]
);

$jane->fill(
name => "jane",
friends => [ $ann ]
);

You will notice that the name field is always the same as the variable
that stores the object. What kind of magic would I have to use in the
constructor (or in the fill() method) so that the name of the object
does not have to be specified explicitly? In other words, I would like
to be able to say:

$joe->fill(
friends => [ $ann, tom ]
);

$jane->fill(
friends => [ $ann ]
);

and still have the name field set. This saves typing and makes sure the
variable name and the name field match.

Sure. Just don't put the objects in separate variables, put them in a
hash.


Even better, bless the hash, iow make it its own class.

Then you get something like:

use Friends;

$friends = new Friends;


$friends->add(
name => "joe",
friends => [ "ann", "tom" ]
);

$friends->add(
name => "jane",
friends => [ "ann" ]
);

Or, if these are the only options, even shorter, either:

$friends->add("joe",[ "ann", "tom" ]);

or even:

$friends->add("joe", "ann", "tom" );

The disadvantage is that you loose the compile time checking wether those
friends exist. You code will not compile if I use $anne instead of $ann,
my suggestion will compile, run and probably not even notice.

What you also can do, is store the name of the Dude in the Dude object.
This is better design anyhow, but does not save you the extra redundancy
of specifying the name twice:

my $joe = Dude->new("joe");
my $tom = Dude->new("tom");
my $ann = Dude->new("ann");
my $jane = Dude->new("jane");

$joe->add_friends($ann, $tom);
$jane->add_friends($ann);


M4
 
K

Koszalek Opalek

Sure. Just don't put the objects in separate variables, put them in a
hash.

Even better, bless the hash, iow make it its own class.

Then you get something like:

use Friends;

I'm afraid that misses the point (or I have misunderstood
you). I do not want to bless my objects into Friends.
I want to bless each one into a signle Dude so that I
can do things like

$joe->whoami()

(And I do not want to rewrite code, that takes it for
granted that $joe, $tim, and $ann have all been blessed
into Dude's.)

What you also can do, is store the name of the Dude in the Dude object.
This is better design anyhow, but does not save you the extra redundancy
of specifying the name twice:

my $joe = Dude->new("joe");
my $tom = Dude->new("tom");
my $ann = Dude->new("ann");
my $jane = Dude->new("jane");


That is not different from what I originally
posted except that (name=>"joe") has been
moved from fill() to new().

Koszalek
 
P

Peter Scott

My code creates new objects and then populates them with
data, like this:

my $joe = Dude->new();
my $tom = Dude->new();
my $ann = Dude->new();
my $jane = Dude->new();

$joe->fill(
name => "joe",
friends => [ $ann, $tom ]
);

$jane->fill(
name => "jane",
friends => [ $ann ]
);

You will notice that the name field is always the same as
the variable that stores the object. What kind of magic
would I have to use in the constructor (or in the fill()
method) so that the name of the object does not have
to be specified explicitly? In other words, I would like to
be able to say:

$joe->fill(
friends => [ $ann, tom ]
);

$jane->fill(
friends => [ $ann ]
);

and still have the name field set. This saves typing and
makes sure the variable name and the name field match.

Is this possible?

Possible, yes. Ugly, too. What you want is a sure-fire sign of a bad
design. I'm only going to show how to do it as an academic exercise to
demonstrate that Perl makes even bad ideas possible. I'm not going to
answer any questions about it. I think it would be a hideous mistake to
put it in production code.

$ cat /tmp/foo
#!/usr/local/bin/perl
use strict;
use warnings;

use PadWalker qw(peek_my);
use List::Util qw(first);

my $my_name = 42;

get_name(\$my_name);

sub get_name {
my $ref = shift;

my $h = peek_my(1);
my $name = first { ref $ref && $h->{$_} eq $ref } keys %$h;
print "Object variable name = ", ($name || "<???>"), "\n";
}

$ /tmp/foo
Object variable name = $my_name
 
M

Michele Dondi

My code creates new objects and then populates them with
data, like this:

my $joe = Dude->new();
my $tom = Dude->new();
my $ann = Dude->new();
my $jane = Dude->new();

$joe->fill(
name => "joe",
friends => [ $ann, $tom ]
);

$jane->fill(
name => "jane",
friends => [ $ann ]
);

You will notice that the name field is always the same as
the variable that stores the object. What kind of magic
would I have to use in the constructor (or in the fill()
method) so that the name of the object does not have
to be specified explicitly? In other words, I would like to
be able to say:

$joe->fill(
friends => [ $ann, tom ]
);

$jane->fill(
friends => [ $ann ]
);

and still have the name field set. This saves typing and
makes sure the variable name and the name field match.

Is this possible?

It *may* be possible with some XS (or PadWalker, but I'm not sure) but
it's horrible, anyway. Actually, your problem is *similar* albeit not
exactly the same, as the variable-in-variable-name one: in that case
one is generally told that symrefs are strictly speaking the solution,
but to use a hash instead. And that seems the viable solution here
too: you have a set of duded identified by their name. Thus, you don't
want the identification bit to be a variable name, since a hash index
is more appropriate. There are tons of ways to do so, but one that
springs to mind is to make new() accept a name, which is reasonable.
Thus

my %dude = map { $_ => Dude->new( name => $_ ) }
qw/joe tom ann jane/;

Then

$dude{joe}->fill( friends => [map $dude{$_}, qw/ann tom/] );
# etc.

Of course you may factor hide all the details behind convenient helper
subs.


Michele
 
K

Koszalek Opalek

Koszalek Opalek wrote:

my @people = (
     Dude->new("joe"),
     Dude->new("tom"),
     Dude->new("ann").
     Dude->new("jane")
);

Alternatively/additioanaly, you might have a "group"
class (set of Dudes) that allow Dudes t be found by name.

No, then instead of saying
$ann->fill( friends => [ $tom, $tim] );
I'd have to say smth like
$ann->fill( friends => Friends->findByName("tom", "tim") );

If your set of names becomes large, declaring all the
variables implied by your original approach becomes ugly.

It will never get very large.

You might be thinking Dudes are some jerks from a database.
In fact they are tokens. The module user has two define
Dudes (tokens) and relationships between them and the
module will produce a parse tree. (Specifying Dudes
using a BNF grammar is not scheduled for this release:)

Koszalek
 
M

Martijn Lievaart

I'm afraid that misses the point (or I have misunderstood you). I do
not want to bless my objects into Friends. I want to bless each one into
a signle Dude so that I can do things like

$joe->whoami()

$friends->{"joe"}->whoami();

I think you misunderstood me.
(And I do not want to rewrite code, that takes it for granted that $joe,
$tim, and $ann have all been blessed into Dude's.)

That's different. That's a requirement that's hard to meet with your
other wish.
That is not different from what I originally posted except that
(name=>"joe") has been moved from fill() to new().

It's better design, that's what different. A Dude normally does not get a
name once he gets friends, he has a name, so the constructor should set
the name.

M4
 
D

Dr.Ruud

Martijn Lievaart schreef:
A Dude normally does not
get a name once he gets friends, he has a name, so the constructor
should set the name.

Best let the constructor only construct, and make a separate initiator
to initialize values.
 
M

Martijn Lievaart

Martijn Lievaart schreef:


Best let the constructor only construct, and make a separate initiator
to initialize values.

I beg to differ. An object should be in a determined state after each
method call (and a constructor is a method call). If an empty Dude makes
sense, no problem. But:

$joe = New Dude;
$joe->init("joe", other parameters);

makes no sense to me. The Dude is ment to be joe, an empty Dude object
makes no sense here.

M4
 
P

Peter J. Holzer

Martijn Lievaart schreef:

But sometimes he gets an additional name when he gets friends ...
Best let the constructor only construct, and make a separate initiator
to initialize values.

.... and he isn't necessarily born with a name.

hp
 
M

Michele Dondi

Best let the constructor only construct, and make a separate initiator
to initialize values.

This is a technical detail. I like to leave constructors as simple as
possibile and I let them *call* an initializator to actually
initialize values. But the comment you're replying to does make
perfectly sense, in terms of UI. Said this, if the OP has a set of
dudes, he may like to implement a Dudes class to which he can add
dudes:

my $dudes = Dudes->new;
$dudes->add(joe => qw/ann tom/);
$dudes->add(jane => qw/ann/);

and let add() do the job. (Including creating suitable Dude objects
for friends if they do not exist.)


Michele
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,209
Messages
2,571,086
Members
47,684
Latest member
Rashi Yadav

Latest Threads

Top