Lady_Aleena has asked for the wisdom of the Perl Monks concerning the following question:
I've have been going around in circles trying to get a grasp on objects, and when I finally go to give it a try, I can not get an object to work as I was hoping.
package Twitter::Objects;
use strict;
use warnings;
use lib '..';
use Base::Data qw(data_file get_hash);
my %accounts = get_hash(
file => data_file('Twitter','account_totals.txt'),
headings => [qw(screen_name followers friends updates)],
);
sub new {
my $self = \%accounts;
bless($self);
return $self;
}
1;
Now, when I go to get data out of that object, I have to type my $foo = Twitter::Objects->new; print $foo->{Lady_Aleena}{updates};. I thought a way to shorten that would be the following.
package Twitter::Objects;
use strict;
use warnings;
use lib '..';
use Base::Data qw(data_file get_hash);
my %accounts = get_hash(
file => data_file('Twitter','account_totals.txt'),
headings => [qw(screen_name followers friends updates)],
);
sub new {
my ($account) = @_;
my $self = \%{$accounts{$account}};
bless($self);
return $self;
}
1;
However, when using the above like so ... my $foo = Twitter::Objects->new('Lady_Aleena'; print $foo->{updates};, I get back an empty string. So, why does the subhash not work but the main hash does?
get_hash
Just in case you want to know.
sub get_hash {
my %opt = @_;
open(my $fh, '<', $opt{file}) or die("can't open $opt{file} $!");
my $line_number = 0;
my %hash;
while (my $line = <$fh>) {
++$line_number;
chomp $line;
my @values = split(/\|/,$line);
my $n = 0;
$hash{$values[0]}{sort_number} = $line_number if $opt{sort};
for my $heading (@{$opt{headings}}) {
$hash{$values[0]}{$heading} = defined($values[$n]) ? $values[$n]
+ : '';
++$n;
}
}
return %hash;
}
Have a cookie and a very nice day!
Lady Aleena
Re: Why won't a hash in a hash work as a hash reference to create an object?
by moritz (Cardinal) on Apr 07, 2012 at 21:12 UTC
|
sub new {
my ($account) = @_;
That's the problem. The first argument that new gets is the name of the class. So instead write
sub new {
my ($class, $account) = @_;
return bless $accounts{$account}, $class;
}
| [reply] [d/l] [select] |
|
| [reply] [d/l] |
|
It's the name of the class. If you call new as a method:
my $obj = Class->new;
... then it'll be the invocant.
(Don't not call new as a method. You set yourself up for all kinds of mess that way.)
| [reply] [d/l] [select] |
|
| [reply] |
Re: Why won't a hash in a hash work as a hash reference to create an object?
by GrandFather (Saint) on Apr 08, 2012 at 21:52 UTC
|
If all you do is use an object as a hash reference then there is not much point making it an object. However, looking at your code it seems there is scope for a useful object. It looks like get_hash is probably general purpose code so it may make sense to base a class around that: a class that wraps up a bunch of options that can be saved to and loaded from disk. Consider:
use strict;
use warnings;
package OptionsBase;
sub new {
my ($class, %options) = @_;
my $self = bless \%options, $class;
$self->{headingsLU} = {map {$_ => 1} @{$options{headings}}};
return $self;
}
sub load {
my ($self) = @_;
open my $fIn, '<', $self->{file} or die ("Can't open $self->{file}
+: $!");
while (defined (my $line = <$fIn>)) {
chomp $line;
my ($rowName, @values) = split (/\|/, $line);
$self->{options}{$rowName}{sort_number} = $. if $self->{sort};
for my $heading (@{$self->{headings}}) {
$self->{rows}{$rowName}{$heading} = shift @values || '';
}
}
}
sub save {
my ($self) = @_;
open my $fOut, '>', $self->{file} or die ("Can't create $self->{fi
+le}: $!");
for my $key (keys %{$self->{rows}}) {
my @values =
map {defined $_ ? $_ : ''}
@{$self->{rows}{$key}}{@{$self->{headings}}};
print $fOut join ('|', $key, @values), "\n";
}
}
sub setOptions {
my ($self, $key, %options) = @_;
my @badOptions = grep {!exists $self->{headingsLU}{$_}} keys %opti
+ons;
die "Bad options: @badOptions\n" if @badOptions;
@{$self->{rows}{$key}}{keys %options} = values %options;
}
sub getOptions {
my ($self, $key, @options) = @_;
my @badOptions = grep {!exists $self->{headingsLU}{$_}} @options;
die "Bad options: @badOptions\n" if @badOptions;
die "No such row: $key\n" if !exists $self->{rows}{$key};
return map {$_ => $self->{rows}{$key}{$_}} @options;
}
package TwitterOptions;
push @TwitterOptions::ISA, 'OptionsBase';
sub new {
my ($class, %options) = @_;
return $class->SUPER::new(
name => 'Twitter',
file => 'account_totals.txt',
headings => [qw(screen_name followers friends updates)],
%options
);
}
package main;
my $obj = TwitterOptions->new();
$obj->setOptions('joe', screen_name => 'Joe', followers => 'Freida');
$obj->save();
my $another = TwitterOptions->new();
$another->load();
my %options = $another->getOptions('joe', 'screen_name', 'followers');
print "$options{screen_name}'s followers are: $options{followers}\n";
Prints:
Joe's followers are: Freida
This sample uses object inheritance to provide some common methods that act on option objects using the base class OptionsBase. TwitterOptions inherits those methods (that's what the ISA stuff is about) so all the methods provided in OptionsBase can be used with TwitterOptions objects. The neat thing now is that you can make new options classes that all behave in the same way, but have different sets of headers and file names etc.
True laziness is hard work
| [reply] [d/l] [select] |
|
GrandFather, I am going to try to explain what I see, but I will probably have some of it wrong, so please correct me where I'm in error.
- It looks like new under package OptionsBase; is just setting the hash keys for the hash that is being created. I'm not exactly sure what headingsLU is doing.
- It looks like load under package OptionsBase; is getting the file and loading it into the hash.
- It looks like save under package OptionsBase; is saving the hash back to the file. (I normally just save without a subroutine, just open and print.)
- I can't tell what setOptions and getOptions are doing. I see an array called @badOptions, but I don't know where the bad options are coming from.
- I take it that I can split this up into two different modules at package TwitterOptions; since my original get_hash is its own beast separate from the Twitter code or would that completely mess up your ISA stuff?</c>
- I'm not exactly sure why you wrote something for modifying the hash, since that will only happen when another script is run which will modify the data file. (Also, followers is a number not a list. There is %followers that has the headings [qw(id screen_name greet)])
The get_hash subroutine is one that I use everywhere in my work to create a hash from my data files. (movies, role playing, miscellany, etc)
I think I'll just give you the bigger picture so you can see what I did and where I did it.
Base::Data is where get_hash and even data_file are.
Since I use get_root a lot in Base::Data, I'll include Base::Roots. That was where I was first trying out objects and couldn't figure it out there.
So, if the top half of what you did could be put in a module on its own, maybe called Base::FileData or something, I would be happy to see how to split this up. You did a lot of work here and added complexities I have yet to understand, but I'll be looking over this over the next few days trying to figure it out.
Have a cookie and a very nice day!
Lady Aleena
| [reply] [d/l] [select] |
|
| [reply] [d/l] [select] |
Re: Why won't a hash in a hash work as a hash reference to create an object?
by sundialsvc4 (Abbot) on Apr 09, 2012 at 15:12 UTC
|
May I say, in all respect and courtesy and politeness, m'Lady, perhaps it is that you are “thinking too deeply into it.” Many object-oriented language implementations impose many strictures upon how the language must work; upon how things must be done. Perl does not. (And there are sound engineering reasons for it, which perhaps do not immediately meet the eye.) Therefore, if you are, perhaps unconsciously, trying to carry-over the concepts taken from those languages into this one, you’re going to wind up mightily confused for a while.
package is a declaration. It simply notifies Perl about a name that is to be associated with all of the code that follows it, superseding any previous package-name or Perl’s default. The significant thing here is that, when you refer to “a Perl object,” that object is always a package. The subroutines in that package are the object’s methods; new is different as previously noted. But the concept of packages, and the concept of objects, are only loosely related!
Start with perldoc perltoot and read that well-written documentation page very slowly and carefully. Especially when it talks about new, which it does several times. Consider, again very slowly and carefully, just what the bless function is actually doing, and why. Of what we mean when we say that something is “a blessed reference,” and, if you like, exactly how the Perl language system represents that notion. (It is literally a bit.)
Many object-oriented languages, as I said, are “object-oriented right down to the freckles on their skin.” They simply can’t be approached in any other way. And yet, here is Perl, and it implements the same concepts using just a small handful of concepts, artfully arranged, and never once does it say to you, “this is the Right Way ... you must do it This Way.” Perl-heads are constantly saying, TMTOWTDI = There’s More Than One Way To Do It, and it takes a long time to grok the sheer genius(!) of what they are talking about. You find yourself (or at least, I certainly did...) grasping around, saying, “where’s the rest of it?!” But, then, you start to realize why this community uses the term, Monks.
| [reply] |
|
sundialsvc4, first thank you for the respect, courtesy, and politeness. You did not have to be any of those things, so I appreciate it. Perl is my first and only programming language, and I am learning programming concepts as I go. I had perltoot up in my browser as I was trying to write that object above, however the example in "Constructors and Instance Methods" did not show how a predefined data source, in my case an HoH, could be connected to an object. Even after reading perltoot, I made my best guess on how to reference a hash in a hash in an object even though my guess was wrong. Had perltoot started with package People; instead of package Person;, I may have gotten the hang of objects sooner.
perltoot starts with a table for one person we know nothing about (in this case Jason), instead of starting with a table for several people for whom we have already gathered data (not only Jason, but his peers Norbert, Rhys, and Phineas too). Had perltoot started with the following and created package people; first, it would have been more enlightening to me.
my $records = [
{
name => "Jason",
age => 23,
peers => [ "Norbert", "Rhys", "Phineas"],
},
{
name => "Norbert",
age => 24,
peers => [ "Jason", "Phineas", "Michael"],
},
{
name => "Rhys",
age => 22,
peers => [ "Norbert", "Jason", "Robert"],
},
{
name => "Phineas",
age => 23,
peers => [ "Norbert", "Jason", "Alexandra"],
},
];
I could possibly take an example object which had an A(ref)oH and converted it to an HoH in my objects. I was having a hard time with perltoot starting in what I consider the end.
Have a cookie and a very nice day!
Lady Aleena
| [reply] [d/l] [select] |
|
|