Как я могу изменить скалярную ссылку, переданную в ссылку на подпрограмму? - PullRequest
4 голосов
/ 18 мая 2010

У меня есть функция для преобразования документов в разные форматы, которая затем вызывает другую функцию на основе типа документа. Это довольно просто для всего, кроме HTML-документов, которые требуют небольшой очистки, и эта очистка зависит от того, откуда она взялась. Поэтому у меня появилась идея, что я мог бы передать ссылку на подпрограмму в функцию convert, чтобы у вызывающей стороны была возможность изменить HTML, вроде как (я не на работе, так что это не копирование и вставка) :

package Converter;
...
sub convert
{
    my ($self, $filename, $coderef) = @_;

    if ($filename =~ /html?$/i) {
        $self->_convert_html($filename, $coderef);
    }
}

sub _convert_html
{
    my ($self, $filename, $coderef) = @_;

    my $html = $self->slurp($filename);
    $coderef->(\$html); #this modifies the html
    $self->save_to_file($filename, $html);
}

, который затем вызывается:

Converter->new->convert("./whatever.html", sub { s/<html>/<xml>/i });

Я пробовал несколько разных вещей в этом направлении, но я продолжаю получать «Использование неинициализированного значения в подстановке (s ///)». Есть ли способ сделать то, что я пытаюсь сделать?

Спасибо

Ответы [ 3 ]

5 голосов
/ 18 мая 2010

Если бы это был я, я бы не стал изменять скалярную ссылку и просто возвращал измененное значение:

sub _convert_html
{
    my ($self, $filename, $coderef) = @_;

    my $html = $self->slurp($filename);
    $html = $coderef->( $html ); #this modifies the html
    $self->save_to_file($filename, $html);
}

Однако, если вы хотите изменить аргументы подпрограммы, стоит знать, что все под аргументы передаются по ссылке в Perl (элементы @_ связываются с аргументами суб-вызова). Таким образом, ваш конверсионный саб может выглядеть так:

sub { $_[0] =~ s/<html>/<xml>/ }

Но если вы действительно хотите работать с $_, как у вас в желаемом примере кода, вам нужно сделать _convert_html() похожим на:

sub _convert_html
{
    my ($self, $filename, $coderef) = @_;

    my $html = $self->slurp($filename);

    $coderef->() for $html;

    $self->save_to_file($filename, $html);
}

for - это простой способ правильно локализовать $_. Вы также можете сделать:

sub _convert_html
{
    my ($self, $filename, $coderef) = @_;

    local $_ = $self->slurp($filename);

    $coderef->();

    $self->save_to_file($filename, $_);
}
3 голосов
/ 18 мая 2010

Помните, что s/// сам по себе работает на $_, но ваша скалярная ссылка передается в подпрограмму обратного вызова в качестве аргумента и поэтому находится в массиве @_.

Таким образом, вы можете просто изменить саблбэк на что-то вроде этого:

sub { my ( $ref ) = @_; $$ref =~ s/<html>/<xml>/i }

Или вы можете воспользоваться псевдонимом аргументов подпрограммы Perl и изменить его напрямую:

sub _convert_html { 
    ...
    $coderef->( $html );
}

, а затем

sub { $_[0] =~ s/<html>/<xml>/i }

(Это фактически изменит исходную строку, если аргумент является скалярной переменной, а не литеральной строкой.)

2 голосов
/ 18 мая 2010

Попробуйте это:

Converter->new->convert("./whatever.html", sub { ${$_[0]} =~ s/<html>/<xml>/i; });

Вы получаете предупреждение о неинициализированном значении, потому что подстановке не дается ничего, с чем можно работать ($_ не определено в своей области) Вы должны указать ему, где найти его значение (в @_, для справки).

Если вы хотите проявить фантазию, вы можете заставить coderef работать со всеми его аргументами по умолчанию:

sub { map { $$_ =~ s/<html>/<xml>/i } @_ }
...