views:

155

answers:

7

My goal is to be able to use $obj like this:

print $obj->hello() . $obj->{foo};

And I would like to create an object inline, maybe using something like this:

my $obj = (
    foo => 1,
    hello => sub { return 'world' }
);

but when I try to use $obj as an object, I get an error saying that $obj has not been blessed. Is there some base class (like stdClass in PHP) I can use to bless the hash so that I can use it as an object?


For those that know JavaScript, I am trying to do the following, but in Perl:

# JS CODE BELOW
var obj = { foo: 1, hello: function () { return 'world' } };
echo obj.hello() + obj.foo;
+2  A: 

$obj would be a scalar, so whatever you assign to it has to be a scalar as well. You could either say

my %obj = ( foo => 1, hello => sub { return 'world' });

or

my $obj = { foo => 1, hello => sub { return 'world' }};

The latter, with the curly braces, creates a hash reference (which is a scalar, so it can go into $obj). To get to the stuff inside a hash reference, though, you have to use the arrow operator. Something like $obj->{foo} or &{$obj->{hello}}.

Unless you need to have lists of hashes or something like that, it's generally better to use the first method.

Either way, you won't be able to say $obj->hello(). Perl uses that syntax for its own flavor of OOP, which would have the hello function in a separate package that your reference has been blessed into. Like:

package example;
sub new {} { my $result = {}; return bless $result, 'example' }
sub hello { return 'world' }

package main;
my $obj = example->new();

As you can see, the methods you can call are already defined, and it's not trivial to add more. There are magic methods you can use to do such a thing, but really, it's not worth it. &{$obj{hello}} (or &{$obj->{hello}} for a reference) is less effort than trying to make Perl work like Javascript.

cHao
Well, this gives me reading material for the rest of the day...
Tom
A: 

Methods in Perl are not properties of the object like they are in Python. Methods are plain regular functions functions in a package associated with the object. Regular functions taking an extra argument for the self reference.

You cannot have dynamically created functions as methods.

Here is a quote from perldoc perlobj:

   1.  An object is simply a reference that happens to know which class it
       belongs to.

   2.  A class is simply a package that happens to provide methods to deal
       with object references.

   3.  A method is simply a subroutine that expects an object reference
       (or a package name, for class methods) as the first argument.

Oh, and bless() is how you establish the connection between the reference and the package.

Arkadiy
+9  A: 

Perl would require a little help to do this. Because it doesn't consider code references stored in hashes as "methods". Methods are implemented as entries into a package symbol table. Perl is more class-oriented than JavaScript, which proudly proclaims that it is more object-oriented (on individual objects).

In order to do that functionality, you would have to create a class that mapped references in this way. The way to get around methods in the symbol table is the AUTOLOAD method. If a package contains an AUTOLOAD subroutine, when a call is made to a blessed object that Perl cannot find in the inheritance chain, it will call AUTOLOAD and set the package-scoped (our) variable $AUTOLOAD will contain the full name of the function.

We get the name of the method called, by getting the last node (after the last '::') of the fully-qualified sub name. We look to see if there is a coderef at that location, and if there is, we can return it.

package AutoObject;

use strict;
use warnings;
use Carp;
use Params::Util qw<_CODE>;
our $AUTOLOAD;

sub AUTOLOAD {
    my $method_name = substr( $AUTOLOAD, index( $AUTOLOAD, '::' ) + 2 );
    my $self        = shift;
    my $meth        = _CODE( $self->{$method_name} );
    unless ( $meth ) { 
        Carp::croak( "object does not support method='$method_name'!" );
    }
    return $meth->( $self, @_ );
}


1;

Then you would bless the object into that class:

package main;

my $obj 
    = bless { foo => 1
      , hello => sub { return 'world' }
      }, 'AutoObject';

print $obj->hello();

Normally, in an AUTOLOAD sub I "cement" behavior. That is, I create entries into the package symbol table to avoid AUTOLOAD the next time. But that's usually for a reasonably defined class behavior.

I also designed a QuickClass which creates a package for each object declared, but that contains a lot of symbol table wrangling that now days is probably better done with Class::MOP.


Given the suggestion by Eric Strom, you could add the following code into the AutoObject package. The import sub would be called anytime somebody use-d AutoObject (with the parameter 'object').

# Definition:
sub object ($) { return bless $_[0], __PACKAGE__; };

sub import { # gets called when Perl reads 'use AutoObject;'
    shift; # my name
    return unless $_[0] eq 'object'; # object is it's only export
    use Symbol;
    *{ Symbol::qualify_to_reference( 'object', scalar caller()) }
        = \&object
        ;
}

And then, when you wanted to create an "object literal", you could just do:

use AutoObject qw<object>;

And the expression would be:

object { foo => 1, hello => sub { return 'world' } };

You could even do:

object { name  => 'World'
       , hello => sub { return "Hello, $_[0]->{name}"; } 
       }->hello()
       ;

And you have an "object literal" expression. Perhaps the module would be better called Object::Literal.

Axeman
This is a handy trick, actually. I might put this to good use, if I ever used OOP in Perl.
Jon Purdy
You could make the calling code a little cleaner by hiding the blessing in a sub: `sub object {bless $_[0] => 'AutoObject'}` and then `my $obj = object {...};`
Eric Strom
@Eric Strom: that makes good sense. But here's my thinking: The JavaScript idiom is basically an "object literal". In Perl, the "object literal" is (often) `bless { ... }, 'MyClass'`. *Of course*, since `bless` is just a func, there is no reason that `object { ... }` isn't a better literal. I'll change my code.
Axeman
+2  A: 

Try Hash::AsObject from CPAN.

deepakg
+1  A: 

In whatever function you're creating the object in, you need to call bless on your object in order to enable method calling.

For example:

package MyClass;

sub new
{
  my $obj = {
    foo => 1
  };

  return bless($obj, "MyClass");
}

sub hello
{
  my $self = shift;
  # Do stuff, including shifting off other arguments if needed
}

package main;
my $obj = MyClass::new();

print "Foo: " . $obj->{foo} . "\n";
$obj->hello();

EDIT: If you want to be able to use subroutine references to provide dynamic functionality for your objects...

First, you can create your code reference like so (within this hash constructor example):

my $obj = {
  foo => 1,
  hello => sub { print "Hello\n"; },
}

You can then invoke it like this:

my $obj = MyClass::new(); # or whatever
$obj->{hello}->(@myArguments);

A little cumbersome, but it works. (You might not even need the second arrow, but I'm not sure.)

Platinum Azure
+2  A: 

It's spelled a little bit differently in Perl:

my $obj = { foo => 1, hello => sub { return "world" } };
print $obj->{hello}() . $obj->{foo};

But the code is awkward. The warning you saw about the reference not being blessed is telling you that your objects aren't implemented in the way Perl expects. The bless operator marks an object with the package in which to begin searching for its methods.

Tell us what you want to do in terms of your problem domain, and we can offer suggestions for a more natural approach in Perl.

Greg Bacon
+1  A: 

A more Perlish approach is to create a separate namespace for your object's desired methods and to bless the object to make those methods available for your object. The code to do this can still be quite succint.

my $obj = bless { foo => 1 }, "bar";
sub bar::hello { return 'world' };

As gbacon suggests, if you're willing to write $obj->{hello}->() instead of $obj->hello(), you can skip the bless operation.

my $obj = { foo => 1, hello => sub { return 'world' } };
mobrule