Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Help creating a function

by varghees (Novice)
on Feb 16, 2011 at 06:17 UTC ( [id://888450]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks, I want one function which accepts package name and hash values, then this function should return the object of type first argument and the values from the second argument. for example
sub GetObject('Employee', $employeeData) { use Employee; $emp = new Employee; $emp.name = $employeeData->{name}; $emp.dept = $employeeData->{dept}; return $emp; }
I want a general solution as it should work for any packages. Thanks

Replies are listed 'Best First'.
Re: Help creating a function
by ELISHEVA (Prior) on Feb 16, 2011 at 11:35 UTC

    I want a general solution as it should work for any packages.

    For that to work, you would have to deal with the problem on both ends: not only would you need an object loader, but also you would need to ensure that all of your objects had the same construction semantics as your loader.

    In Perl some objects are backed by hashes and some are not. There are objects that are backed by arrays, scalar references, code references, io handles, and much more. You can't even assume that the objects store their own data. For a while something called inside-out objects were fashionable - instead of storing their data they used their reference id or some other id to look up their field values in arrays and hashes stored at the class level.

    Even if the data is stored in a per-object hash, you wouldn't necessarily want to set the fields directly. The constructor might be doing some error checking or generating derived data, or other setup that will be missed if you bypass the constructor method and assign values to hash keys directly.

    Existing partial solutions

    There are some partial solutions already done for you, but they depend on your object's storage format. For example, if you dump class data in a YAML file and then reload it, the reloaded data will automatically be blessed into classes. In this case bypassing the constructor isn't so much of an issue because the dump process took a snapshot of data that was already set up by the constructor. You can also use Data::Dumper, Data::Storable and Data::Serializable (with YAML/Storable/Dumper serialization) this way.

    If you are loading data from a database and you know there is a one-to-one relationship between objects and rows in the view you are retrieving, you could use selectrow_hashref or selectall_arrayref($sql, { Slice => {} }) to load in a hash and then bless it to whatever class you wish, e.g.

    my $obj = bless($dbh->selectrow_hashref("SELECT ..."), 'Employee');

    Since we are loading from a database we can assume (?) that the database's stored procedures and table/field constraints have already ensured that the data is validated. If that is something that interests you, then I recommend that you open your database handle with RaiseError => 1 so that exceptions are thrown if the class fails to load. If you are willing to have all your objects use Class::DBI you can even get automatic conversion of queries to objects. Inheriting from Class::DBI would also let you do additional derivations and validations after the automated population of data from the database.

    Rolling your own

    If you wish to roll your own solution, it will pay to think long and hard about the scope of your object loader tool. Where is your data coming from? What storage mechanisms do you want to use? Is this going to be used only by classes you write? Or will other people be using this object loading tool for their own classes? Are you going to load only objects written for the tool's conventions, or do you need to load any arbitrary object ever written for Perl?

    If you are only loading your own objects and you are the only person ever using this, I would recommend just deciding on a constructor convention and sticking with it. One word of warning though: experiment.

    Despite its common use, there are a lot of drawbacks to the constructor model in your example (creating an empty object and setting properties). One big issue is that validations that rely on the values of two or more fields can be tricky to get right. Instead of one constructor that looks at both values at the same time, you have to have write a validation/derived value calculation routine and then have both property setter methods call that validation anytime the property is set. Sounds easy enough, but it isn't.

    It is far too easy to forget to call the validation in one of the setter methods. You may never even notice the bug until you have corrupted data. It can also lead to performance issues: if two values contribute to a derived value and they are set separately, then the derivation is recalculated each time a property is set. Even if you get both properties at once and could in theory set them together, you can't because your interface only lets you set one property at a time.

    If this loader has to be used by others, it is important that whatever constructor semantics you choose, you will need to document them like crazy and also be prepared to spend time teaching and explaining them to others (or to the tech writer if you are working together with one).

    If this loader has to work with any old object, then strict rules about how to create objects won't work for you. You'll have to come up with a mechanism to (a) define the constructor semantics for a class (b) store that definition in your object loader (c) retrieve the definition when an object of a particular class is requested (d) apply that definition to that particular hash and class.

    Even if you are the only one to use this, it would be a good idea to document it carefully. You'll find (speaking from experience here) that there are quite a number of different construction strategies you'll have to consider and hence quite a number of different ways to define your rules. You will in effect be writing a domain specific langauge.

    I don't mean to scare you off, but I just want you to understand that what you are asking is not just "a function". Especially if you go for the most general solution, you run the risk of spending more time writing and debugging the code than you would hand crafting constructor calls for your project.

    Also unless you know the whole area of object construction in depth, you are likely to end up with a tool that gets productivity gains in one area only to take them away in others. Object have lots of ways of being built because they support a very wide variety of data and processing needs. Any object loader tool that doesn't respect that will end up tying programmers into knots forcing them to program to the tool rather than to the real performance and data flow constraints of their problem domain

Re: Help creating a function
by samarzone (Pilgrim) on Feb 16, 2011 at 07:25 UTC

    Are you working on Perl? Above code snipped will not compile in Perl 5.

    bless or eval may be of interest for you. If the data structure passed in arguemnts(e.g. $employeeData) has exactly the same structure as of the underlying object of the given package, you can directly bless it. If not, you'll have to first create some rules to set the properties of object from given data structure and then "eval"

    The cost of genericness may be that you know the underlying structure of package and pass the correct arguments.

    --
    Regards
    - Samar
      yeah I know it will not compile in perl. I just used pseudo code in that example. Second parameter can have more values also. It can have some values like $employeeData=>{create} like that. Also I want the return object should be the type of first argument.
Re: Help creating a function
by cdarke (Prior) on Feb 16, 2011 at 10:52 UTC
    The problem is that new is not a Perl keyword. A package can call it's constructor anything it likes, Bob for example.

    You can create a bless'ed reference to any package, but you would need to know what type it should reference (scalar, array, hash, whatever), and how to initialise the structure.
Re: Help creating a function
by zwon (Abbot) on Feb 16, 2011 at 10:50 UTC

    generally you can do something like this:

    use Module::Load; sub get_object { my ($class, $href) = @_; load $class; return bless $href, $class; }
    but, it will not work for any package. Also instead of bless you can call a constructor:
    return $class->new($href);
    but again, it won't work for any package.
Re: Help creating a function
by tospo (Hermit) on Feb 16, 2011 at 11:01 UTC
    I think you are looking for a factory pattern. Here is a suggestion:
    package People; sub new { my ($classname, $type, $data_ref) = @_ ; my $class = $classname . '::' . $type ; use $class; return $class->new( $data_ref ) ; } # new ####################### package People::Employee; sub new{ ... } sub name{...} ... ###################### In a script far, far away: use People; my $employee = People->new( 'Employee', {name => 'Someone',...}); print $employee->name;
      Yes. I am looking for factory pattern. But this code
      my $class = $classname . '::' . $type ; use $class;
      is not working in perl. Throws syntax error.
        oops sorry, yes that was wrong. Actually what I normally do is this:
        $class=~s/:{2}/\//g; $class.= '.pm'; require $class_path;
        But there is probably a better way of doing it.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (6)
As of 2024-04-24 11:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found