Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Tiny Frameworks

by Ovid (Cardinal)
on Sep 29, 2007 at 18:25 UTC ( #641718=perlmeditation: print w/replies, xml ) Need Help??

Rather than have a reply buried somewhere in this thread where it might get missed, I bow to consensus and and agree that I was wrong. Sorry for all of the hullabaloo :) (though I see from comments below that not everyone disagreed with my post)

It seems that the work "framework" has fallen out of fashion in some circles. In fact, many programmers sneer at the thought of frameworks. This is disappointing because a framework, to borrow the Wikipedia definition, is merely a conceptual structure to solve a complex issue.

We might think of things like J2EE, Catalyst, or other things as frameworks and be scared of them. This is sad because those can be great tools, but the important thing is to understand the problem you are trying to solve and decide if the framework is applicable to this problem. In reality, general purpose frameworks such as Catalyst often offer a lot more than you need or solve problems in a different way from what you expect. The might be huge and have a steep learning curve or they might be so simple as to not solve your needs. In this meditation, I want to describe a very common problem and offer a simple framework to solve it. In fact, the framework is so simple that it's merely a small class, but it can save much work later on.

Disclaimer: I'm not going to pretend that my framework is a general purpose solution for you or that it's the right solution for any problem. In fact, I can definitely point out some flaws in in, but it's merely intended as an example of one way of bringing structure to your code. I'm also going to touch on the highlights of the code and let you work out the details for yourself.

The problem: you want to quickly prototype classes for your code in a way that is easy to learn.

The solution: a tiny superclass.

Some of you might argue that a single superclass is not a framework. However, remember that a framework is merely a way to solve a complex problem. This does not mean that the framework must be complex. The simple framework that I am presenting offers the following:

  • Built-in class data
  • Blessed hashrefs (because all OO programmers know them)
  • A standard constructor
  • Easy validation of presence of constructor arguments
  • Easy getter/setters

The framework is simplistic, but that's the point. What it brings is a consistency to class construction. As a result, if all of your classes are based on it, programmers coming into your project easily learn how to create and use new classes rather than wondering "what's the constructor name? How do I handle class data? What are the constructor arguments? How are errors handled?"

First, let's call the class My::UNIVERSAL. It's intended that most if not all of your classes will inherit from this or from another class which does. We're not sticking anything directly into UNIVERSAL because we don't want to introduce a global change which might have unexpected side-effects on modules which don't use this.

Now, the first thing we want to do is handle class data (strict and warnings should be included but are omitted to keep the examples simpler):

package My::UNIVERSAL; use base qw(Class::Data::Inheritable); 1;

Now every class which inherits from this gets class data for free. That's not a huge win, but we can also get accessor/mutator method generation for free:

package My::UNIVERSAL; use strict; use warnings; use base qw( Class::Data::Inheritable Class::Accessor ); 1;

Now that sort of works, but there are some problems with this:

package Creature; use base 'My::UNIVERSAL'; __PACKAGE__->mk_classdata( mortal => 1 ); __PACKAGE__->mk_accessors( qw/name gender/ ); 1; # in the code my $creature = Creature->new({ name => 'Alice', gender => 'female', } );

In the above example, the resulting data structure looks like this:

$VAR1 = bless( { gender => 'female', name => 'Alice' }, 'Creature' );

For this simple example, this seems fine, but as many who have worked on large code bases can attest, accidentally overwriting a hash key in a blessed hash can cause bugs which are very painful to track down. Further, Class::Accessor, while useful, has mutators which return the value we're setting the objects to. In other words:

print $creature->gender('male');   # prints 'male'

If you like to chain method calls, that doesn't work.

As an added issue, passing unknown hash keys results in those appearing in blessed hash. We'll deal with that later, but for now, let's dispense with Class::Accessor and write our own. We want it to make keys local to the class we're using and to return the invocant (the object) when setting a value.

sub mk_accessors { my ( $class, @accessors ) = @_; foreach my $accessor (@accessors) { no strict 'refs'; my $key = "$class\::$accessor"; *$key = sub { my $self = shift; return $self->{$key} unless @_; $self->{$key} = shift; return $self; }; } }

With this, all keys in the hashref are now prepended with the package name. Now this might seem like a pain to type out, but if you use those methods internally in your class, it's not so bad. Imagine a Rectangle class:

sub area { my $self = shift; return $self->height * $self->width; }

That's easy to read and pretty self-documenting.

Getting rid of Class::Accessor means we no longer have a default constructor, so let's add one.

use Scalar::Util 'reftype'; sub new { my ( $class, $args ) = @_; $args = {} unless defined $args; unless ( 'HASH' eq reftype $args ) { croak "Argument to new() must be a hash reference"; } my $self = bless {} => $class; while ( my ( $key, $value ) = each %$args ) { $self->$key($value); } return $self; }

And the constructor does not change, but look at the object now:

$VAR1 = bless( { 'Creature::gender' => 'female', 'Creature::name' => 'Alice' }, 'Creature' );

Now instance data is per class and we can chain method calls if we prefer this style (separate method calls are always fine):

$creature->name('Bob') ->gender('unknown');

Also, let's look at what happens if we try to add an unknown key:

my $creature = Creature->new( { name => 'Alice', gender => 'female', foo => 1, } );

This results in the error Can't locate object method "foo" via package "Creature". Very handy!

At this point, we have a useful but simplistic class creation framework. However, there are a couple of features we want. Perhaps "name" is mandatory but "gender" is not. Let's add a couple of helpers for this. The code here is going to get a bit magical for a couple of reasons. First, we want something that's easy to use and sometimes you need to do funky things under the covers. Second, in refactoring out some common code, we can lose the return value of wantarray because, unlike caller, wantarray does not allow you to inspect higher stack frames. Thus, we'll use a "magic" goto to ignore the existing stack frame (see the documentation of goto for an explanation) We'll also have to change our constructor for this.

__PACKAGE__->mk_class_data('__data_key'); sub new { my ( $class, $args ) = @_; # XXX I don't like doing this for every instance, but # use 'base' doesn't call import() my $key = " $class data "; $class->__data_key($key); $args ||= {}; unless ( 'HASH' eq reftype $args ) { croak('Argument to new() must be a hashref'); } my $self = bless { $key => $args } => $class; $self->_initialize; delete $self->{$key}; return $self; } sub _initialize { my $class = ref($_[0]); croak "_initialize() must be overridden in a ($class)"; } # a truly private method! my $check_attribute = sub { my ( $self, $attribute ) = @_; my $value = delete $self->{ $self->__data_key }->{$attribute}; if ( ! defined wantarray ) { $self->$attribute($value); } else { return $value; } }; sub _must_have { my ( $self, $attribute ) = @_; croak("Mandatory attribute ($attribute) not found") unless exists $self->{ $self->__data_key }->{$attribute}; goto $check_attribute; } sub _may_have { my ( $self, $attribute ) = @_; return unless exists $self->{ $self->__data_key }->{$attribute}; goto $check_attribute; }

We now create a subclass as follows:

package Creature; use base 'My::UNIVERSAL'; __PACKAGE__->mk_classdata( mortal => 1 ); __PACKAGE__->mk_accessors(qw/name gender/); sub _initialize { my ( $self, $args ) = @_; $self->_must_have('name'); $self->_may_have('gender'); }

Pretty simple, eh? If we're missing 'name' in the constructor, creating the class will fail. The 'gender' attribute is still optional.

But what's with the wantarray stuff? Well, it's possible that the accessor has a different name, so we support that:

package IP::Address; use base 'My::Universal'; __PACKAGE__->mk_accessors('address'); sub _initialize { my $self = shift; $self->address( $self->_must_have('ip') ); } 1; # use IP::Address; my $ip = IP::Address->new({ ip => $some_ip }); print $ip->address; # prints $some_ip

Of course, having different accessors for property names can quickly get confusing, but it's now there if you need it.

Another problem we want to solve is passing unknown attributes to the constructor. Because our constructor is deliberately destructive to the hashref passed into it, this makes this simple. We'll add a private $check_keys method and tweak our constructor a little.

my $check_keys = sub { my $self = shift; my $data = delete $self->{ $self->__data_key }; if ( my @keys = keys %$data ) { local $" = ', '; croak("Unknown keys to constructor: (@keys)"); } }; sub new { my ( $class, $args ) = @_; my $key = " $class data "; $class->__data_key($key); $args ||= {}; unless ( 'HASH' eq reftype $args ) { croak('Argument to new() must be a hashref'); } my $self = bless { $key => $args } => $class; $self->_initialize; $self->$check_keys; return $self; }

And let's see what happens with this:

my $creature = Creature->new( { name => 'Alice', gender => 'female', foo => 1, bar => 1, } ); # Unknown keys to constructor: (bar, foo)

We have now met all of our original design goals. There are plenty of other improvements we could make. Standard _carp and _croak methods might be useful. Perhaps proper exception handling would be good. And, of course, if we wanted this to be a serious class, we would have to modify mk_accessors to allow for proper data validation:

package Tall::Square; use Scalar::Util 'looks_like_number'; use base 'My::UNIVERSAL'; __PACKAGE__->mk_accessors( width => sub { looks_like_number($_[1]) && $_[1] > 0 ), height => sub { my ( $self, $height ) = @_; return looks_like_number($height) && $height > $self->width; }, ); sub _initialize { my $self = shift; $self->_must_have('height'); $self->_must_have('width'); } sub area { my $self = shift; return $self->height * $self->width; } 1;

Adding that is left as an exercise to the reader.

This simple class construction framework is not intended to be a serious framework (you might consider Moose or something similar), but it does show that frameworks need not be complex and they can reduce the tedium of 'grunt work' that can come with writing code. Remember that just because general purpose frameworks are often huge and have more features than you need, creating and using one for your personal or professional code does not have to be a bad thing.

package My::UNIVERSAL; use strict; use warnings; use Scalar::Util 'reftype'; use Carp 'croak'; use base qw(Class::Data::Inheritable); __PACKAGE__->mk_classdata('__data_key'); sub mk_accessors { my ( $class, @accessors ) = @_; foreach my $accessor (@accessors) { no strict 'refs'; my $key = "$class\::$accessor"; *$key = sub { my $self = shift; return $self->{$key} unless @_; $self->{$key} = shift; return $self; }; } } my $check_keys = sub { my $self = shift; my $data = delete $self->{ $self->__data_key }; if ( my @keys = keys %$data ) { local $" = ', '; croak("Unknown keys to constructor: (@keys)"); } }; sub new { my ( $class, $args ) = @_; my $key = " $class data "; $class->__data_key($key); $args ||= {}; unless ( 'HASH' eq reftype $args ) { croak('Argument to new() must be a hashref'); } my $self = bless { $key => $args } => $class; $self->_initialize; $self->$check_keys; return $self; } sub _initialize { my $class = ref($_[0]); croak "_initialize() must be overridden in a ($class)"; } my $check_attribute = sub { my ( $self, $attribute ) = @_; my $value = delete $self->{ $self->__data_key }->{$attribute}; if ( !defined wantarray ) { $self->$attribute($value); } else { return $value; } }; sub _must_have { my ( $self, $attribute ) = @_; croak("Mandatory attribute ($attribute) not found") unless exists $self->{ $self->__data_key }->{$attribute}; goto $check_attribute; } sub _may_have { my ( $self, $attribute ) = @_; return unless exists $self->{ $self->__data_key }->{$attribute}; goto $check_attribute; } 1;

Update: Fixed a typo noticed by calin whereby I referred to a superclass as a subclass.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Tiny Frameworks
by BrowserUk (Pope) on Sep 30, 2007 at 14:58 UTC

    As one of those who has expressed skeptisim about "frameworks" here, I'll have a go a producing a definition. Or rather, a comparison between a 'framework' and a 'library'.

    • A framework provides the common infrastructure supporting a definied architecture for a class of applications.

      (IMO) the key element of a framework is that it provides the structure of an application and leaves the application programmer to fill in the details of the specific application.

      As the label suggests, this is analogous to the framework, infrastructure of (say) an office building within which you can conduct any type of business. You would normally require a different type of framework for a domestic building or a warehouse operation etc.

    • A library (or abstract class) provides a service to any application that needs that service.

      A library encapsulates the detail of some specific service or algorithm and allows the application programmer to ignore the details. It can be called from any kind of application.

      This is analogous to (say) the telephone system. The system stays essentially the same (whilst varying in scale), regardless of the purpose of the building (application).

    In a nutshell, frameworks call you; you call libraries.

    Whilst what you describe provides the infrastructure for the creation of classes, the classes produced using it would usually be more akin to libraries. That is, classes built using your infrastructure would themselves be components within larger applications, and there would often be several such derived classes within any moderately sized application.

    So whilst it is possible to see how you arrived at the term 'mini-framework', my gut feel about what constitutes a framework is that they provide the outer shell and overall structure for an application along with the stubs for essential plumbing. Viewed this way, your module doesn't really live up to the tag. As useful as it is, I would consider it the wrong label.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      While I knew my post would be controversial, the distinction I'm not seeing from people is the dividing line between "library" and "framework" (in the same way, perhaps, that many cannot define "pornography" or "enterprise code"). Perhaps I'm being a bit slow, but the only significant difference I see between our definitions of "framework" is that yours is more verbose. There's a certain irony there :)

      In my case, the "class of applications" this code provide a defined architecture for is "all OO applications." I'm not limiting myself to Web apps, ORMs, or something similar. So using less code to support a greater range of applications than most frameworks support somehow makes it not a framework? Believe me, I did my reading on this and I'm convinced that the word "framework" is misleading the hell out of people. It's a buzzword I want to smash under my foot.

      So I provided what I refer to as a "tiny" framework. Imagine at some point that someone comes along and provides better method validation. Someone else comes along and adds integrated exception handling. Someone else comes along and adds a persistence mechanism. Someone else comes along and calls it a framework. At that point, some people jump on the bandwagon and some people sneer.

      If the mere act of calling something a framework makes it a framework, then my code qualifies. If it doesn't make something a framework, then when in the above sequence of events did it become one and why? There's nothing magical about frameworks. There is no minimum size/complexity/buzzword requirement for them. However, it seems to me that in the back of people's minds they seem to think that a framework must somehow be big and restrict the scope of things that can be worked on -- of course, mine doesn't do anything for non-OO code, so maybe it qualifies? :)

      Oh, and thanks for actually providing your own definition, unlike the Anonymous Monk above. I appreciate that :)

      Cheers,
      Ovid

      New address of my CGI Course.

        Is pointillism a frame? Pointillism is a technique to use when making paintings. A frame gives you a structure to put your painting inside of. It is not a coincidence that the word "framework" contains the word "frame". BrowserUK's definition was not just "more verbose". It was also certainly less vague. It made great points about the type of structure a framework is.

        Ugh1

        I'll either somewhat disagree with or somewhat augment what BrowserUK said by saying that his definition sounds like what I would call an "application framework" and there certainly could be a "library framework". But a "library framework" would need to define the "outer shell" and over-all structure of the library and provide ways for people to put different guts inside of that. And the term "framework" as applied to software is most often used to mean "application framework" (IMHO, of course).

        I'm unsure whether using "framework" for non-application frameworks is desirable / useful terminology. Certainly, people shouldn't be afraid of developing software in a structured manner, though.

        - tye        

        1 Update: Ooh, that could be taken quite harshly; like I was saying Ovid is worse than an idiot. That certainly wasn't my intent and I apologize. I certainly don't belive Ovid is an idiot. I think Ovid should re-read BrowserUK's description. And I made note of the fact that one anonymous reply re-stressing part of the distinction that I think Ovid may not have fully appreciated.

            A reply falls below the community's threshold of quality. You may see it by logging in.
        A reply falls below the community's threshold of quality. You may see it by logging in.

      Boy, I'm coming late to this party. Sorry about that. The following is asked in the interest of understanding your (and by extrapolation Tye's) points. It may seem that I'm supporting Ovid's point, but really the most accurate statement of my position is "confused".

      If in the examples in the OP, the data about the Creature package were extracted to, say, XML, could you then call this a "framework for declarative creation of classes"? It seems to me that this constitutes the details to be filled in by the application programmer. I haven't used Hibernate, but wouldn't that be just one level down from what Hibernate does (declarative objects, plus how they relate, whereas this would be just declarative objects)? Does it matter whether those details are provided by the application programmer as XML (like in my hypothetical) or as structured code (like the original example)?

      I guess part of Ovid's point was to explore the grey areas in the use of the term and now I feel caught up in that gery area. Again, I ask for the sake of clarification. I don't have skin in the game either way.

      Goibhniu stops previewing, takes a deep breath and pushes create . . .


      I humbly seek wisdom.

        Hmm. From my perspective this is all about terminology. Let's try an analogy:

        When they built The Swiss Re Tower, some company was charged with the manufacture of the 5500 triangular and diamond-shaped glass panels that form the outer cladding. As these panels vary in size for each of the 40 floors, there was no doubt some mechanism for inputing the required sizes for each of the aluminium extrusions that make up the panels to the (probably digitally controlled) cutting machines.

        In terms of the manufacture of the panels, that mechanism may have been seen as some kind of a "framework". But in terms of the construction of the building, the term "framework" was probably reserved for the steel 'diagrid' that forms the overall shape and load-bearing structure for the building.

        There are already many terms for tools that are used to construct classes: Library; toolkit; Meta class; Specification language; Class Factory; etc. All of which make more or less sense depending upon the philosophy behind them; the interface they present; the way they operate; and the way they are intended to be used. Do a CPAN search: Class::* and read the first few lines of each module (say the first couple of hundred) for examples of all these and more.

        There seems little purpose in taking a term 'framework', that already has a fairly intuative interpretation, and qualifying it with 'tiny', in order to bend it to fit a conceptual space that is already heavily overloaded with terminology.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

      A very good point, framework calls you, and you call library.

      Framework is a set of rules and conventions, you supply the details to the framework, and you expect the framework to call your detailed implementation according to the rules at the right time for the right thing.

      Bravo! Now I know why we didn't drown in the sea of frameworks as I said: most people are wise.

Re: Tiny Frameworks
by kyle (Abbot) on Oct 01, 2007 at 01:39 UTC
      A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Tiny Frameworks
by dsheroh (Prior) on Oct 01, 2007 at 15:18 UTC
    Although I see you've already bowed to consensus, Ovid, I'd like to thank you for raising the question. Despite the anonymonk's insistence that you're stupid and foolish to have done so instead of simply following "commen sense"(sic), I find that progress can come only from questioning the status quo.

    Back on-topic, I do agree with the general consensus that your example is not what I would call an (unqualified) "framework", as I interpret a "framework" as something which tells you how you should create your code and, in the process, necessarily constrains that code. (If you don't comply with its constraints, you can't use whatever services the framework provides.) My::UNIVERSAL does not tell you how to structure your code overall, nor does it lead to any wide-reaching constraints, so I don't believe it qualifies as an application framework, which, as tye pointed out, is what is normally meant when "framework" is used alone.

    I could, however, go along with calling it a very-light-weight OO framework, as it does suggest and provide a structure for the OO implementation of code which uses it. The constraints it places are very few and very likely to be well-repaid with increased functionality for decreased effort, so I'd even call it a well-designed OO framework. But it's still a framework for OO only, not a general framework.

      A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Tiny Frameworks
by toma (Vicar) on Oct 02, 2007 at 02:08 UTC
    Ah, a framework flame fest! I remember those from the CAD business in the early 1990's. At first, frameworks were touted as platforms for interoperability between different vendor's products. This definition didn't seem to conform to reality.

    A definition for 'CAD Framework' that I heard in the late 1990's is that a framework is the 'outer window' of a CAD application. CAD tools often have a lot of little windows that are confined inside a larger window. So whatever structure implements the outermost window of the application is the framework. With this type of definition, you could say, for example, that a web browser is a framework for a web application.

    For business lock-in, many software vendors want to own and control the framework. For example, microsoft has ie/iis to lock in their web products.

    Some of the derision that people feel towards frameworks may come from the difference between the hype: 'The framework is a platform for interoperability' and the reality: 'The framework is the mechanism for vendor lock-in.'

    IIRC There is another definition of framework from AI that may be a little closer to Ovid's idea.

    It should work perfectly the first time! - toma
      A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Tiny Frameworks
by dmorgo (Pilgrim) on Oct 03, 2007 at 05:52 UTC
    My, how the years have flown here at the monastery since this writeup was created.

    Now that the framework-or-not-framework question has been beaten to death, can I ask a naive question? What modules are people using for class creation these days, and why?

    A reply falls below the community's threshold of quality. You may see it by logging in.
    A reply falls below the community's threshold of quality. You may see it by logging in.
    A reply falls below the community's threshold of quality. You may see it by logging in.
    A reply falls below the community's threshold of quality. You may see it by logging in.
    A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (8)
As of 2018-12-10 14:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    How many stories does it take before you've heard them all?







    Results (49 votes). Check out past polls.

    Notices?
    • (Sep 10, 2018 at 22:53 UTC) Welcome new users!