Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Your favorite objects NOT of the hashref phylum

by blogical (Pilgrim)
on Mar 23, 2006 at 15:19 UTC ( [id://538764]=perlquestion: print w/replies, xml ) Need Help??

blogical has asked for the wisdom of the Perl Monks concerning the following question:

Hello all, What objects do you find useful that aren't blessed hash refs? Is there a certain genus of object you find best suited to a scalar? Have you come across a particularly useful or funky species of coderef object?
I've found using various CPAN modules a great way to examine the use objects in Perl, but would like to see some more diversity so that I can get a better handle on why different foundations might be better in different situations.

blogical

Her lips were red, her looks were free, her hair was yellow gold.
  • Comment on Your favorite objects NOT of the hashref phylum

Replies are listed 'Best First'.
Re: Your favorite objects NOT of the hashref phylum
by xdg (Monsignor) on Mar 23, 2006 at 15:36 UTC

    Well, there's the whole inside-out object approach, which can use just about any type of blessed object -- though often just a blessed scalar. See Class::InsideOut or Object::InsideOut for good implementations.

    For my inside-out objects talk, I wrote File::Marker as an example of using a blessed filehandle as the underlying type.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Your favorite objects NOT of the hashref phylum
by codeacrobat (Chaplain) on Mar 23, 2006 at 15:57 UTC
    A stopwatch for example.
    package Stopwatch; sub new{bless \$_[1], $_[0]} sub diff{`date +%s` - ${$_[0]}} $a=new Stopwatch(`date +%s`); sleep rand()*10; print "I slept for ", $a->diff, " seconds\n";

      How about just using the builtin Perl function time instead of backticks and the unix date function?

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        Thanx, sometimes I forget the simplest things. I just needed a quick example illustrating the Stopwatch. I guess the reason I forgot about time is that there are so many similar functions (time, times, gmtime, localtime, asctime, mktime, andwhatnottime).
        It's good to have the backticks if you got lost but know a non-perl alternative.
        I remember using date in a log function once as a Perl junior. I finally noticed it because my code was taking a while to run, so I did my first ever profiling run on it. The backtick date was consuming 60% of the CPU time for the entire process.
Re: Your favorite objects NOT of the hashref phylum
by dokkeldepper (Friar) on Mar 23, 2006 at 16:52 UTC
    For all this fixed dimension geometry stuff I use arrayrefs.
    package Location; use integer; use constant X=>0; use constant Y=>1; sub new{ my($self,$x,$y)=@_; bless[$x,$y],$self; } # this part can be obviously autoloaded sub get_X{ my $self=shift; return $self->[X]; } sub get_Y{ my $self=shift; return $self->[Y]; }
Re: Your favorite objects NOT of the hashref phylum (array w/ consts)
by tye (Sage) on Mar 23, 2006 at 20:43 UTC

    I prefer a ref to a blessed array with constants noting which member data is stored at which array index:

    ... my $offset; BEGIN { $offset= 0; # Or BaseClass->_GetMaxOffset(); for my $attrib ( qw/ foo bar baz / ) { eval "sub _$attrib() { $offset }; 1" or die $@; $offset++; } } sub _GetMaxOffset { $offset } # Example method sub Foo { my $me= shift @_; my $old= $me->[_foo]; if( @_ ) { my $new= shift @_; # validate here $me->[_foo]= $new; } return $old; }

    You can get compile-time catching of mispelt attribute names, faster and more compact objects, minimal increase in code complexity.

    Updated: Dropped the extra "my" inside the BEGIN block as pointed out by vhold (Thanks!).

    - tye        

      For compile time catching of typos in attrs when using hashes as the internal representation use the fields module.

      In perl 5.8 it's based on pseudohashes, which are a bit like mapping strings into arrays, except they use hash syntax. But everyone knows that pseudohashes suck ;-)

      In perl 5.9 fields uses Hash::Util and locks the hash for runtime checks, and also performs the same compile time checks, so you get the benefits of fields without the insanity of pseudohashes.

      The way you use it is:

      use fields qw/attr1 attr2/; sub new { # ... my __PACKAGE__ $self = fields::new($pkg); # construct using fields +::new $self->{attr1} = "foo"; # ok $self->{attttr2} = "bar"; # compile time error } sub some_method { my __PACKAGE__ $self = shift; # use __PACKAGE__ or any class name +to tell perl what class $self is $self->{foo}; # because we told it to make compile time checks bas +ed on __PACKAGE__'s fields this will be checked at compile time }
      But this still doesn't let you encapsulate the properties in subroutines that you can override.
      -nuffin
      zz zZ Z Z #!perl
      This is my favorite approach, and It's good that you show the extra hoop you have to jump through to support inheritence, but watch out that you did "my $offset" twice, making _GaxMaxOffset always return undef.
      How do you handle multiple inheritance?

      Perhaps with the Moose module you can write a metaclass that uses named syntax for attributes, but compiles the offsets as late as possible in a way that takes care of MI, much like $any_static_language's vtables are compiled.

      -nuffin
      zz zZ Z Z #!perl

        I strongly dislike class-making frameworks that everyone and their dog wants to bolt onto the side of Perl. So I'm not going to be writing any plug-ins for one. I've never seen any significant value in any of them and most of them look quite prone to break things due to ETOOMUCHMAGIC (and I've seen several actually break things), so I'm quite happy to avoid all of them completely. I don't find it particularly challenging to implement reasonable classes using just Perl 5 and I like that understanding my code doesn't require understanding some extra framework first.

        I try to (and succeed at) avoiding inheritance in most cases, especially in Perl. Inheriting interfaces (aka pure-virtual classes) is a fine thing. Inheriting from a class that contains code is very often not the best solution and I (and many other experienced developers) know all too well how inheritance binds very tightly and can lead to brittle designs that become a burden.

        So, if Perl had interfaces I might write Perl classes that inherit from more than one set of interfaces. Since Perl doesn't have (non-trivial) interfaces, I only rarely use inheritance in Perl and I have never been tempted to use multiple inheritance in Perl.

        If I had new-fangled near-inheritance tools (traits, mix-ins, or whatever you want to call them), I'd probably make use of those more often.

        I find that I'm more successful in the long run when I bend my approach to a problem to fit the language that I'm solving it in, rather than trying to bend the language to fit my first choice of approach. So I write Perl 5 classes in Perl 5 OO and easily resist the urge to try to turn Perl into my ideal OO framework, even though Perl often appears to make it somewhat easy for you to bend it.

        - tye        

Re: Your favorite objects NOT of the hashref phylum
by hv (Prior) on Mar 23, 2006 at 17:28 UTC

    Here's a simple one I use, to locally override something:

    package MyApp::Local; use strict; sub umask { my($class, $umask) = @_; my $old = umask $umask; $class->new(sub { umask $old }); } # ... and various other more application-specific methods sub new { my($class, $cb) = @_; bless $cb, $class; } sub DESTROY { my $self = shift; $self->(); undef; } 1;

    Example use:

    ... { # create the file, must be world-writable my $temp = MyApp::Local->umask(0); my $fh; sysopen($fh, $path, O_WRONLY | O_CREAT | O_EXCL, 0666) or die "$path: $!\n"; close $fh; # umask restored when $temp goes out of scope, even by dying } ...

    No particular reason this couldn't be a { callback => $cb } hashref, just didn't see the need.

    Hugo

Re: Your favorite objects NOT of the hashref phylum
by stvn (Monsignor) on Mar 24, 2006 at 13:12 UTC

    Personally, I think one of the reasons Perl 5 OO has such a bad name is that you have to make such "manual" descisions yourself. In most other OO languages, these kind of descisions are made for you. This is excactly why I wrote Moose.

    package Stopwatch; use strict; use warnings; use Moose; use DateTime; has 'timer' => ( is => 'rw', isa => 'DateTime', default => sub { DateTime->now } ); sub diff { my $self = shift; $self->time - DateTime->now; } # ... my $sw = Stopwatch->new; # ... wait a moment print $sw->diff;
    In this example, the a default constructor is already available for you (from Moose::Object), instance attributes are managed by the underlying metaclass (as they are in most all other OO languages) which means that and an accessor for the "time" slot has been created (which is read/write, will only accept "DateTime" objects, and has a default value of "DateTime->now").

    The ugly details of how all this happens should be of no concern for your everyday programmer. The detail of the instance storage (it actually is a blessed HASH) should be of no conern to your everyday programmer either. In short, it should all Just Work.

    -stvn
      The ugly details of how all this happens should be of no concern for your everyday programmer. The detail of the instance storage (it actually is a blessed HASH) should be of no concern to your everyday programmer either. In short, it should all Just Work.

      Such bold statements are fine if all your applications

      1. Run for a couple of seconds or minutes at most.
      2. Create a few dozen or few hundred largish objects at most.
      3. If it is convenient, easy and economic to throw hardware at them to alleviate memory and performance bottlenecks.

      But if your application does not fit into this mode of operation; is long running, uses hundreds of thousands or millions of small objects; by it's very nature, pushes the boundaries of both memory and performance of retail hardware to simply hold it's data before you add the overhead of the OO implementation; requires algorithms that need to constant access the entire range of that data with multiple passes; and does not lend itself to being spread across clustered or networked solutions.

      For these types of applications, the mechanisms of OO implementation and the the memory and performance overheads they incur are of considerable concern.

      By way of example. Many of the problems of bio-genetics involve taking millions of fragments of DNA, totalling 1 or two GB of data, and attempting to match their ends and so piece together the original sequence. Just loading the raw data starts to push the boundaries of retail hardware to it's limits.

      Exhaustively iterating all the subsequences of each and comparing them against each other requires an in-memory solution, as splitting the processing across multiple hardware is complex and hugely costly in terms of the communications overhead. Such exhaustive explorations often run for days or even weeks. Ignoring the costs of objectization, or looking at RDBMS solutions to alleviate memory and/or communications concerns can extend those time periods to months.

      The notion of using genetic programming techniques, applying the power of statistics and intelligently random mutation and generational algorithms, as a replacement for the exhaustive, iterative searches is an attractive one. Genetic algorithms can produce impressive results in very short time periods for other NP hard, iterative problems--travelling salesman; knapsack problem etc.

      The idea of "throwing the subsequences into a cauldron" and letting them talk to each other to find affinities, in a random fashion, scoring the individual and overall matches achieved and then "stirring the pot" and letting it happen over lends itself to making each subsequence an object. If each subsequence of a few 10s of bytes of data is going to be represented by a blessed hash, with it minimum overhead of approx. 300 bytes, then you're only going to fit around 7 million in the average PCs memory. If you instead store the sequences in a single array, and use a blessed, integer scalar as the object representing them, the per object overhead of the OO representation falls to approx. 56 bytes giving you room for something like 38 million.

      Will that saving be enough to allow the algorithm to run in memory? For some yes, for others no, but the beauty of Perl's "manual" OO, is that it gives you the choice to balance the needs of your application against the doctrines of OO purity.

      Most OO languages do not give you that choice and so they "Just work", until they don't. And then you're dead in the water facing the purchase of expensive hardware that can handle more memory (and the memory to populate it), or making your program an order of magnitude more complex and several orders of magnitude slower by using a clustered or networked solution.

      Perl gives you the possibility to address problems at their source, by modifying the choices you make in your own source code. And you can do it today without having to wait 3 months for the Hardware Acquisitions committee to approve your Capital Expenditure Request, or the Finance dept. to get the budget; or go through the Corporate Software Approvals process to lay your hands on the clustering software you need :)

      Blessed array indexes as object handles, and direct access to instance data may not be pc as far as OO doctrine is concerned, but a blessed scalar is a blessed scalar regardless of whether it points to a hash that holds a key that indexes into a table that points to the data; or is just a direct reference to the data. And whilst getters and setters to access instance data may prove useful in isolating applications from implementation details, for library classes that will have a long life and are likely to be refactored. For many, perhaps most applications, the level of refactoring that would benefit from that will never happen, and the benefits of that isolation will never be realised.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        BrowserUk

        Well, all your points are well taken. Obviously you should always use the right tool for the job. But I am actually not sure that Moose is the wrong tool for you. Because Moose uses metaclasses to build all instances, and those same metaclasses also build the accessors, there is great opportunity for optimizations here.

        As I mentioned, Moose uses Class::MOP, which builds blessed HASH based instances, but this is just the default. It is possible to extend Class::MOP to build other kinds of instances as well. In the examples for Class::MOP, I show how it can be used to build inside-out classes. I also have an example of a Lazy class which will not initialize it's fields until the absolute last possible moment. I have been considering an ARRAY based example as well, but haven't gotten around to it. There is also nothing to stop you from an Inline::C based version which possibly could be made even more space/time efficient than an array version.

        As for how all this will work with Moose, allow me to explan. The primary role of Moose is to collect meta-data about your class definition, and create a number of metaobjects from it. (The extra overhead of these metaobjects will usually be fairly small, since they are a per-class cost, and not a per-object/instance cost, and most systems will have a reasonably small amount of classes compared to the amount of object/instances they generate. But I digress here ...). These Moose metaobjects are used to build things like accessors, and eventually to help build the actual instances too. Since Moose only makes you pay for the features you use, if, for instance, you don't choose to use a type constraint, you don't pay for it's overhead. Now, since all the details of your class is stored as meta-data by Moose, and the Moose metaobjects are managing the details of your object instance structure and means of accessing it (through accessors), it is possible to swap a different Class::MOP based engine into Moose and have it create ARRAY based instances without having to change the surface syntax of your Moose based classes (assuming you don't break encapsulation that is).

        Now, the example I describe is not easily accomplished at this point, because I have not added the proper hooks into Moose for his kind of thing. But Class::MOP has been designed to do this kind of thing from the very start, so its just a matter of getting the tuits to do it.

        Moose and Class::MOP are tools designed to work with Perl 5's OO system and not against it. This means that they should not get in you way if you need/want to do something different, because after all, TIMTOWTDI :)

        -stvn
        Thanks a lot BrowserUK, that's exactly the sort of answer I was looking for! Very well put.

        obey the law
        <flameRetardent>
        First let me say that I think that all the ways of making objects discussed here are good choices in many situations.
        </flameRetardent>

        I just wanted to make a comment that many systems do just deal with a relatively small number of objects at once - most web based systems for example.

        I once used an in-house object system similar to what Moose sounds like, but with even more features and overhead (it also included automatic RDBMS mapping etc). It worked fine in the web based system, but then we had to make some batch jobs using the same objects for migration. We had a few hours to build, modify and then tear down hundreds of thousands of objects.

        The system was too slow - it was going to take about 2 days! But wait - theres more! I spent a few days profiling the code and came up with a few smallish changes. I tweaked the most used methods to use more efficient mechanisms. Some methods were being run literally millions of times - in those cases I threw niceness to the wind and removed lexically scoped variables, reached into objects bypassing accessors etc. The were mostly small methods and I made up for the ugliness with large wads of code comments and POD to ensure that the code remained maintainable.

        2 days of execution then became 2 hours. I also did some major tweaking on the RDBMS side, but at least half of the performance gain was due to the perl code changes.

        My point is that you should normally not throw out a code model that benefits your developers because of concerns with future scalability. Unless the model is stupid there is usually a way to make it fast after the fact. This is not always true in other languages where you are contstrained in your options, but in Perl there is always a way to optimise more. If you really need to, you can do wacky things like manipulate the runtime tables or rewrite your most often used methods in XS, but I've never had to do that (which is a pity because it could be fun).

      With all due respect -- as I'm sure that Moose is techinically excellent -- I don't really understand how it is really any different from a practical perspective than most of the other full-featured class system generators for Perl.

      You define a syntax for users to create constructors, accessors, etc. You've got some clean syntax and some stronger type-checking than some class builders, true, but the basic concept of providing an interface to generate a class hasn't changed a lot since, say, Class::MethodMaker (which goes back to 1996).

      With almost any full system, you get the accessors and constructors 'for free' and don't need to know the details. For example:

      package Stopwatch; use strict; use warnings; use DateTime; use Class::MethodMaker [ scalar => [ { -type => 'DateTime', -default_ctor => sub { DateTime-> +now }, 'timer' ], new => 'new', ]; sub diff { my $self = shift; $self->timer - DateTime->now; } # ... my $sw = Stopwatch->new; # ... wait a moment print $sw->diff;

      Only when you move away from using accessors to get at encapsulated data does the underlying form really matter. For example, with inside-out objects:

      package Stopwatch; use strict; use warnings; use DateTime; use Class::InsideOut qw( public register id ); public timer => my %timer => { set_hook => { $_->isa('DateTime') or die "must be a DateTime object" + } }; sub new { my $self = register( bless \(my $s) ); $timer{ id $self } = DateTime->now; return $self; } sub diff { my $self = shift; $time{ id $self } - DateTime->now; } # ... my $sw = Stopwatch->new; # ... wait a moment print $sw->diff;

      Could you help me understand what's really different about Moose beyond syntax?

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        Could you help me understand what's really different about Moose beyond syntax?

        Moose is built on top of Class::MOP (a metaclass system for Perl 5), which means that Moose has support for deep class introspection. So for instance, you can do this:

        my $type_constraint = Stopwatch->meta->get_attribute('timer')->type_co +nstraint; print $type_constraint->name; # prints DateTime
        And since the $type_constraint returned is itself a meta-object, you can get plenty of information from it as well, such as it's parent types (NOTE: this is not reflective of any class/@ISA hierarchy in Moose, it is instead just type/subtype relationships defined in Moose::Util::TypeConstraints)
        print $type_constraint->parent->name; # prints Object print $type_constraint->parent->parent->name; # prints Ref print $type_constraint->parent->parent->parent->name; # prints Any
        While type information like this is not directly useful for your everyday programmer, it is very useful for tools like ORMs. I am actually working closely with mst, who is the author of DBIx::Class, to explore the possibilities of using this deep introspection from Moose with DBIx::Class somehow.

        But Moose has many other features as well, not just automatic accessor generation. It also can create arbitrary type-constraints, which are not directly linked to classes in the system. Here is an example from the Moose test suite:

        subtype Protocol => as Str => where { /^HTTP\/[0-9]\.[0-9]$/ };
        This is a subtype of the built-in Moose type 'Str' which will only accepts strings which look like HTTP protocol declarations. And you can use these in your attribute declarations just the same as class names, so this will DWIM.

        has 'protocol' => (isa => 'Protocol');

        Because Str and Protocol are just a type "constraints", we avoid the unessecary overhead of creating a class for this. Moose also has type-coercion features as well, although those are still fairly experimental, if you interested I suggest looking here to see a good example of it's usage.

        Moose also offers CLOS style before/after/around method modifiers, which allow for AOP like method wrapping. An example of that can be found here in a recent use.perl posting of mine. And version 0.03 of Moose (due out later this week), will have support for BETA style "inner" calls (think of these as the inverse of "super" calls, /msg me if you want more details).

        And to top this all off, Moose will be (although it not currently, it's only 0.02 after all) completely pluggable. Meaning you will be able to (for instance) subclass Moose::Meta::Attribute to either change existing behaviors, or add new behaviors to change how Moose handles attributes. This is all due to the fact that Moose is built on Class::MOP, which is built on the theoretical foundations of other (somewhat more acedemic) object systems like CLOS and Smalltalk (which themselves were built by people far smarter than I will ever be).

        In short, Moose is just the syntactic sugar on top of a fully dynamic and reflective object system for Perl 5.

        -stvn
      A moose once bit my sister...
Re: Your favorite objects NOT of the hashref phylum
by nferraz (Monk) on Mar 25, 2006 at 21:49 UTC

    I don't know if it's the kind of object you're looking for, but I'm very interested in interators:

    my $iterator = &foo(); while($iterator->()) { # do something }

    The cool part is the way they can be created in Perl:

    sub foo { return sub { # some code }; }

    Now, you may be asking what's an iterator used for? Iterators can be used instead of arrays when the list in its entirety would use too much memory, or when the list is infinite.

    For example, the set of even numbers:

    my $even = &even_numbers(1000); while (my $n = $even->()) { print "$n\n"; sleep(1); } sub even_numbers { my $number = shift; $number-- unless ($number % 2); return sub { $number += 2 }; }

    I know this is not the most sophisticated example, but you got the idea.

    If you want to learn more about iterators, this is a good start point:

    http://www.perl.com/pub/a/2005/06/16/iterators.html

Re: Your favorite objects NOT of the hashref phylum
by perrin (Chancellor) on Mar 27, 2006 at 18:49 UTC
    One of the most widely used modules with a non-hash OO implementation is Template Toolkit. It uses array refs. I thought this was pretty slick at the time. These days I've come to think that using these array objects was probably not worth the extra time in coding and maintenance. I doubt they were much of a performance gain. Still, kind of neat-looking.
Re: Your favorite objects NOT of the hashref phylum
by ikegami (Patriarch) on Mar 29, 2006 at 17:32 UTC

    I have two modules that use scalar objects:

    package OnDestroy; BEGIN { our @EXPORT = qw( on_destroy ); require Exporter; *import = \&Exporter::import; } sub on_destroy(&) { my ($action) = @_; return bless(\$action); } sub cancel { my ($self) = @_; undef $$self; } sub DESTROY { my ($self) = @_; my $action = $$self; $action->() if $action; } 1;

    and something I wrote in reponse to a question on PerlMonks:

    package AutoExecute; BEGIN { our @EXPORT = qw( auto_execute ); require Exporter; *import = \&Exporter::import; } use overload '""' => \&stringify, '&{}' => \&code_ref, ; sub auto_execute(&) { my ($sub) = @_; return bless(\$sub); } sub new { my ($class, $sub) = @_; return bless(\$sub, $class); } sub stringify { my ($self) = @_; my $sub = $$self; return $sub->(); } sub code_ref { my ($self) = @_; my $sub = $$self; return $sub; } 1;

    All my other objects are based on arrays.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://538764]
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (2)
As of 2024-04-19 18:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found