Perl: Как создавать объекты на лету? - PullRequest
12 голосов
/ 04 августа 2010

Моя цель - использовать $obj так:

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

И я хотел бы создать встроенный объект, возможно, используя что-то , например this:

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

но когда я пытаюсь использовать $obj в качестве объекта, я получаю сообщение об ошибке, говорящее, что $ obj не был благословлен . Есть ли какой-нибудь базовый класс (например, stdClass в PHP), который я могу использовать, чтобы благословить хеш, чтобы использовать его как объект?


Для тех, кто знает JavaScript, я пытаюсь сделать следующее, но на Perl:

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

Ответы [ 8 ]

14 голосов
/ 04 августа 2010

Perl потребуется небольшая помощь, чтобы сделать это. Потому что он не рассматривает ссылки кода, хранящиеся в хешах, как «методы». Методы реализованы в виде записей в таблице символов пакета. Perl более ориентирован на класс , чем JavaScript, который с гордостью заявляет, что он более объектно-ориентирован (для отдельных объектов).

Чтобы выполнить эту функцию, вам нужно создать класс, который отображал бы ссылки таким образом. Способ обойти методы в таблице символов - метод AUTOLOAD. Если пакет содержит подпрограмму AUTOLOAD, то при вызове благословенного объекта, который Perl не может найти в цепочке наследования, он вызовет AUTOLOAD и установит переменную области пакета (our) $AUTOLOAD содержит полное имя функции.

Мы получаем имя вызываемого метода, получая последний узел (после последнего '::') полностью определенного имени. Мы смотрим, есть ли кодовая ссылка в этом месте, и если она есть, мы можем ее вернуть.

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 )    = @_;
    my $meth        = _CODE( $self->{$method_name} );
    unless ( $meth ) { 
        Carp::croak( "object does not support method='$method_name'!" );
    }
    goto &$meth;
}


1;

Тогда вы бы благословили объект в этот класс:

package main;

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

print $obj->hello();

Обычно в AUTOLOAD sub I "цементирует" поведение. То есть я создаю записи в таблице символов пакета, чтобы избежать AUTOLOAD в следующий раз. Но это обычно для разумно определенного поведения класса.

Я также разработал QuickClass, который создает пакет для каждого объявленного объекта, но в нем много разборок таблиц символов, что сейчас дни, вероятно, лучше сделать с Class::MOP.


Учитывая предложение Эрика Строма, вы можете добавить следующий код в пакет AutoObject. Подставка import будет вызываться каждый раз, когда кто-нибудь use -d AutoObject (с параметром '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
        ;
}

И затем, когда вы захотите создать «литерал объекта», вы можете просто сделать:

use AutoObject qw<object>;

И выражение будет:

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

Вы могли бы даже сделать:

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

И у вас есть выражение "литерал объекта". Возможно, модуль будет лучше называться Object::Literal.

4 голосов
/ 04 августа 2010

Более подход Perlish заключается в создании отдельного пространства имен для желаемых методов вашего объекта и bless объекта, чтобы сделать эти методы доступными для вашего объекта.Код для этого все еще может быть довольно лаконичным.

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

Как предполагает gbacon, если вы хотите написать $obj->{hello}->() вместо $obj->hello(), вы можете пропустить операцию благословения.

my $obj = { foo => 1, hello => sub { return 'world' } };
4 голосов
/ 04 августа 2010

Попробуйте Hash::AsObject от CPAN.

2 голосов
/ 04 августа 2010

В Perl написано немного по-другому:

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

Но код неудобен. Предупреждение о том, что ссылка не благословлена, говорит о том, что ваши объекты не реализованы в , как Perl ожидает . Оператор bless помечает объект пакетом, в котором начинается поиск его методов.

Скажите нам, что вы хотите сделать с точки зрения вашей проблемной области, и мы можем предложить предложения по более естественному подходу в Perl.

2 голосов
/ 04 августа 2010

$obj будет скаляром, поэтому все, что вы назначаете для него, также должно быть скаляром.Вы можете сказать:

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

или

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

Последний с фигурными скобками создает ссылку на хеш (которая является скаляром, поэтому она может перейти в $obj),Тем не менее, чтобы добраться до вещей внутри хеш-ссылки, вы должны использовать оператор стрелки.Что-то вроде $obj->{foo} или &{$obj->{hello}}.

Если вам не нужны списки хэшей или что-то подобное, обычно лучше использовать первый метод.

В любом случае, вы выиграли 'не сможет сказать $obj->hello().Perl использует этот синтаксис для своего собственного вида ООП, который будет иметь функцию hello в отдельном пакете, в который была введена ваша ссылка bless.Например:

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

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

Как вы можете видеть, методы, которые вы можете вызвать, уже определены, и добавить их нетривиально.Есть магические методы, которые вы можете использовать, чтобы сделать такую ​​вещь, но на самом деле, это того не стоит.&{$obj{hello}} (или &{$obj->{hello}} для справки) - это меньше усилий, чем пытаться заставить Perl работать как Javascript.

1 голос
/ 04 августа 2010

В любой функции, в которой вы создаете объект, вам нужно вызвать bless для вашего объекта, чтобы разрешить вызов метода.

Например:

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();

РЕДАКТИРОВАТЬ: Если вы хотите использовать ссылки подпрограмм для обеспечения динамической функциональности ваших объектов ...

Сначала вы можете создать ссылку на код следующим образом (в этом примере хеш-конструктора):

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

Затем вы можете вызвать его так:

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

Немного громоздко, но это работает.(Возможно, вам даже не понадобится вторая стрелка, но я не уверен.)

0 голосов
/ 23 августа 2015

Я рекомендую использовать Class :: Struct, как описано на man-странице perltoot.

Вместо того, чтобы перефразировать документацию, позвольте мне процитировать ее, поскольку она хорошо объяснила, как это работает:

"Что этодействительно предоставляет вам способ «объявить» класс как имеющий объекты, поля которых имеют определенный тип. Функция, которая делает это, называется, что неудивительно, struct (). Поскольку структуры или записи не являются базовыми типами в Perl,каждый раз, когда вы хотите создать класс для предоставления объекта данных, похожего на запись, вы сами должны определить метод new (), а также отдельные методы доступа к данным для каждого из полей этой записи. Вам быстро надоест этот процесс. Функция Class :: Struct :: struct () облегчает эту скуку. "

Все еще цитирование из документа - пример того, как его реализовать:

use Class::Struct qw(struct);
use Jobbie;  # user-defined; see below
struct 'Fred' => {
    one        => '$',
    many       => '@',
    profession => 'Jobbie',  # does not call Jobbie->new()
};
$ob = Fred->new(profession => Jobbie->new());
$ob->one("hmmmm");
$ob->many(0, "here");
$ob->many(1, "you");
$ob->many(2, "go");
print "Just set: ", $ob->many(2), "\n";
$ob->profession->salary(10_000);
0 голосов
/ 04 августа 2010

Методы в Perl не являются свойствами объекта, как в Python.Методы - это обычные обычные функции-функции в пакете , связанном с объектом.Обычные функции, принимающие дополнительный аргумент для ссылки на себя.

Вы не можете иметь динамически созданные функции как методы.

Вот цитата из 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и bless () - это способ установления соединения между ссылкой и пакетом.

...