in reply to Re^2: OOP first-timer seeks feedback; likes long walks on the beach.
in thread OOP first-timer seeks feedback; likes long walks on the beach.

The disapproved manner ... check this thread A few Perl OOP questions. for more info. I'll leave it at that. ;-)

The caller thing - I'm not sure about speed. But I don't see caller used very often, so it's probably not well understood. That said, I merely mentioned the way I'd normally approach the problem and did not mean to imply that yours was inferior. There's "clever" and "too clever". Where the line is drawn, however, depends on one's experience ;-)

Here's what I think a reasonable usage of this paradigm would look like from the caller's perspective. Note that this is not intended to be the only reasonable usage, just an example of one reasonable usage:

# here's the pre portion... my $count; my %data; IterateOver::file({ loop => sub { if (/^(.*?)\s*=\s*(.*?)\s*(?:#.*)$/) { $data{$1} = $2; } }, $some_file ); # post goes here...
The only purpose of having a pre and a post is when you have loops inside of loops - so the pre and post are executed prior to the loop commencing, and after the loop is completed, respectively.

Thus, the example:

my @files = ( ... ); my $count; my %data; IterateOver::files({ pre => sub { print "Starting on file $_.\n"; $count = 0; }, loop => sub { if (/^[^#]/ and /^(.*?)\s*=\s*(.*?)\s*(?:#.*)$/) { $data{$1} = $2; ++$count; } }, post => sub { print "Found $count keys in $_.\n"; print "Total unique keys so far: ", scalar keys %data, "\n"; }, @files );
Of course, this is completely not an object. To be honest, right-tool-for-right-job does not tell me that OO is the best way to do this in perl (and I'm an OO bigot ... at least from my C++ days!). Closures are way easier to use for this.

Don't get me wrong, I've done this in an object in perl. But that's because I was part of a framework, and my framework was already object oriented (oddly enough, this is the third time today I've talked about TaskManagers and Tasks). I have a package that does iteration over some common data, and then calls object methods (not passed in but predefined) as hooks into various parts of the loop - including pre-filter, filter, and post-filter. There's no need to pass in callbacks because the loop function is inside another module dedicated to this task. It looks something like this (see above for the layout of Task):

package TaskFilter; use base 'Task'; sub prefilter { 1 } # default pre is to do nothing and succeed at it. sub postfilter { 1 } # same thing sub filter { die "need to implement filter in subclass '" . ref $_[0] . "'\n"; } sub get_object_list { # generic implementation common to all my tasks derived from here. } sub sort_object_list { # to allow subclass to change order if needed. sort @_ } sub perform_task { my $self = shift; $self->prefilter() or die "prefilter failed."; my @records = $self->sort_object_list($self->get_object_list()); for my $r (@records) { $self->filter($r); } $self->postfilter() or die "postfilter failed."; 1; } 1;
To use this, you derive from TaskFilter your own package. And then you have a $self to use - and that $self really is your filter object. As in your filter object. Thus it makes perfect sense to modify object variables inside object methods, so you can do whatever you want with it - update %$self, etc.

Does that help?

Replies are listed 'Best First'.
Re^4: OOP first-timer seeks feedback; likes long walks on the beach.
by eff_i_g (Curate) on Sep 09, 2005 at 00:49 UTC
    /me mumbles "I knew getting into this OOP stuff would snowball."

    I'm going home. I'll re-read your reply a few dozen times tomorrow :) I printed out one of the links to review--Thanks!
Re^4: OOP first-timer seeks feedback; likes long walks on the beach.
by eff_i_g (Curate) on Sep 09, 2005 at 19:46 UTC
    I think I'm gaining some understanding...

    Your second code example works because the anonymous subs are within the same lexical scope; therefore, there is no need to store the variables they work with separately (in $self, like I did). Correct? I like that.

    The last block of code looks similar to mine, one of the differences being that you only call $self->filter($r) within the loop. Wouldn't the code within filter() be same difference to what I'm doing? That is, I still need an increment and a hashing, so filer() would contain ++$self->{count}; and so forth?

    I believe I'm lost somewhere in between "This is only natural inside a single object, however your callbacks are all external to your object, so that doesn't make a bunch of sense to me." and "As in your filter object. Thus it makes perfect sense to modify object variables inside object methods, so you can do whatever you want with it." The latter statement says to me: "There are no closures (external thingies) involved, therefore everything happens within your methods," which makes me think "Well then, how do i keep this dynamic? Each filtering process is going to be 80% common, but there is some variation."

    Thanks for your time.

    P.S. I am reading through perltoot, perlobj, a few tutorials on this site, and the llama.
      Your second code example works because the anonymous subs are within the same lexical scope; therefore, there is no need to store the variables they work with separately (in $self, like I did). Correct? I like that.

      Precisely. And, if also notice, that second code example also is extremely similar to the way you're using your Hasher, but with a lot less overhead, more intuitive (I think), and no usage of $self.

      The last block of code looks similar to mine, one of the differences being that you only call $self->filter($r) within the loop. Wouldn't the code within filter() be same difference to what I'm doing? That is, I still need an increment and a hashing, so filer() would contain ++$self->{count}; and so forth?

      I see no evidence of not gaining understanding (understand that! ;-}); you nailed this one as well. The difference is that in the last block of code, the expectation is not that you'll give me coderefs from which I can call back to do stuff, but that you'll use inheritance to create a brand new package, and in that package, you'll name certain methods the names that I expect, and I'll call those as object methods at the appropriate places.

      I believe I'm lost somewhere in between "This is only natural inside a single object, however your callbacks are all external to your object, so that doesn't make a bunch of sense to me." ...

      Remember, like perl, English is context sensitive. (Sorry - I know it's the other way around, but it was funnier this way.) The context of this statement was talking about using "$self" inside callbacks - this is rarely done. Callbacks are generally done via closures in perl, and, because they're closures, they have access to all lexical variables at the point of their creation - if you need data to persist past the function call, you simply set it in your closure. For another example, see the File::Find module which uses this idea for the wanted routine.

      ... and "As in your filter object. Thus it makes perfect sense to modify object variables inside object methods, so you can do whatever you want with it." The latter statement says to me: "There are no closures (external thingies) involved, therefore everything happens within your methods," which makes me think "Well then, how do i keep this dynamic? Each filtering process is going to be 80% common, but there is some variation."

      Dynamic - depends on how you want to do it. There are so many ways to do it that I can't really tell you what the best way is - it will depend on what you're trying to do, and the framework you're doing it in (both framework as in any object frameworks you may be using, such as POE, and framework as in how the rest of your code is laid out, the context, if you will.)

      One way is to have different packages derived from your Filter package. You can then instantiate each one ("my $f = FilterThis->new()") and then execute it.

      You can then find commonalities between each of your filter packages and push them into a new base class such that FilterThis is derived from CommonFilter which itself is derived from Filter. This is fine - I do this, and my Filter is actually dervied from Task, so you can keep going as much as you want. It's a little convoluted as a first foray into OO, but trust me, this provides a lot of flexibility.

      Or, you can go simple - a halfway between procedural and OO. (Again, I may be an OO bigot, but this is often the right solution, so don't let my moniker scare you away from this option even while trying to learn OO.) This is to create a few packages derived from Filter, but each one would be drastically different from each other - any commonalities have been put into the filter function for each group. For example, if you were filtering a bunch of websites and a bunch of files, you could have three filter packages: FilterWebHTML, FilterWebXML, and FilterFlatFile. Then, when you create each one, you pass in the variable that applies, e.g., my $f = FilterWebXML->new('http://www.perlmonks.org/index.pl?displaytype=xml;node_id=3333'). You can create lots of each type of object which filters differently from each other, but have the commonality split out based on logic (HTML, XML, Flat files are all filtered differently) vs parameters (the location of each file).

      my @htmls = qw( ... ); my @xmls = qw( ... ); my @flats = qw( ... ); my @filters; push @filters, map { FilterWebHTML->new($_) } @htmls; push @filters, map { FilterWebXML->new($_) } @xmls; push @filters, map { FilterFlatFile->new($_) } @flats; $_->perform_filter() for @filters;
      And in the new method, you just have to store the filename/URL to be filtered in $self somewhere.

        As aforementioned, this is getting to be too much (in perl? never! ...yes!)

        What I have now is nearly the same as before; instead of keeping the "limbo" data in $self, it is now in %data which the method declares. I decided against the closure route because it would not work without declaring the variables before the anonymous subs. (Read: I'm trying to make this as easy to use as possible. No required declares on their part, no variable mix-ups, etc.) Is there anything horribly taboo in my methodology or is this decent? I need to get this nailed down soon, and without overcomplication. Thanks!--Here's the new:

        Class:
        #!/usr/bin/perl package Hasher; use Carp; use strict; use warnings; sub new { my $class = shift; my $self = { @_ }; bless ($self, $class); return $self; } sub hash { my $self = shift; croak 'file does not exist' if not -e $self->{file}; my %data; $data{count} = 0; $data{hash} = {}; $self->{pre}->(\%data) if $self->_verify('pre'); open my $fh, '<', $self->{file} or croak "could not open $self +->{fi le}: $!"; while (<$fh>) { chomp; $data{record} = $_; $self->{loop}->(\%data) if $self->_verify('loop'); ++$data{count}; } $self->{post}->(\%data) if $self->_verify('post'); return %{$data{hash}}; } sub _verify { my $self = shift; my $key = shift; return 1 if ( exists $self->{$key} && ref($self->{$key}) eq 'CODE' ); return 0; } 1;
        Script:
        #!/usr/bin/perl use strict; use warnings; use Hasher; my $states = Hasher->new( file => 'states', loop => sub { my $data_ref = shift; my ($value, $key) = split ' ', $data_ref->{record}, 2; $data_ref->{hash}->{ uc $key } = $value; }, post => sub { my $data_ref = shift; print "hashed $data_ref->{count} states\n"; } ); my %state = $states->hash(); map { print "$_ = $state{$_}\n"; } keys %state;
        use inheritance to create a brand new package, and in that package, you'll name certain methods the names that I expect, and I'll call those as object methods at the appropriate places.

        Wow... this is getting too deep; I think I'll go your closure way on Monday:
        my @files = ( ... ); my $count; my %data; IterateOver::files({ pre => sub { print "Starting on file $_.\n"; $count = 0; }, loop => sub { if (/^[^#]/ and /^(.*?)\s*=\s*(.*?)\s*(?:#.*)$/) { $data{$1} = $2; ++$count; } }, post => sub { print "Found $count keys in $_.\n"; print "Total unique keys so far: ", scalar keys %data, "\n"; }, @files );
      s/llama/alpaca/; # thanks bart