Может ли подпрограмма Perl возвращать данные, но продолжать обработку? - PullRequest
6 голосов
/ 30 апреля 2010

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

Ответы [ 7 ]

7 голосов
/ 30 апреля 2010

Распространенным способом реализации этого типа функциональности является функция обратного вызова:

{
    open my $log, '>', 'logfile' or die $!;
    sub log_line {print $log @_}
}

sub process_file {
    my ($filename, $callback) = @_;
    open my $file, '<', $filename or die $!;
    local $_;
    while (<$file>) {
        if (/some condition/) {
             $callback->($_)
        }
        # whatever other processing you need ....
    }
}

process_file 'myfile.txt', \&log_line;

или даже без указания обратного вызова:

process_file 'myfile.txt', sub {print STDERR @_};
4 голосов
/ 30 апреля 2010

Некоторые языки предлагают такую ​​функцию, используя «генераторы» или «сопрограммы» , но Perl этого не делает. На приведенной выше странице генератора приведены примеры на Python, C # и Ruby (среди прочих).

3 голосов
/ 30 апреля 2010

Модуль Coro выглядит так, как будто он был бы полезен для этой проблемы, хотя я понятия не имею, как он работает, и не знаю, делает ли он то, что рекламирует.

2 голосов
/ 30 апреля 2010

Самый простой способ сделать это в Perl, вероятно, с помощью решения типа итератора. Например, здесь у нас есть подпрограмма, которая формирует замыкание над дескриптором файла:

open my $fh, '<', 'some_file.txt' or die $!;
my $iter = sub { 
    while( my $line = <$fh> ) { 
        return $line if $line =~ /foo/;
    }

    return;
}

Подпрограмма выполняет итерации по строкам, пока не найдет строку, соответствующую шаблону /foo/, а затем вернет ее, иначе ничего не вернется. (undef в скалярном контексте.) Поскольку дескриптор файла $fh определен вне области действия подпрограммы, он остается резидентным в памяти между вызовами. Самое главное, его состояние, включая текущую позицию поиска в файле, сохраняется. Таким образом, каждый вызов подпрограммы возобновляет чтение файла, в котором она остановилась в последний раз.

Чтобы использовать итератор:

while( defined( my $next_line = $iter->() ) ) { 
    # do something with each line here
}
0 голосов
/ 30 апреля 2010

А как насчет рекурсивного саба? Повторное использование open существующих файловых дескрипторов не сбрасывает номер строки ввода, поэтому оно продолжается с того места, где оно было остановлено.

Вот пример, где подпрограмма process_file распечатывает разделенные пробелами "\n\n" абзацы, содержащие foo.

sub process_file {

    my ($fileHandle) = @_;
    my $paragraph;

    while ( defined(my $line = <$fileHandle>) and not eof(<$fileHandle>) ) {

        $paragraph .= $line;
        last unless length($line);
    }

    print $paragraph if $paragraph =~ /foo/;
    goto &process_file unless eof($fileHandle);  
       # goto optimizes the tail recursion and prevents a stack overflow
       # redo unless eof($fileHandle); would also work
}

open my $fileHandle, '<', 'file.txt';
process_file($fileHandle);
0 голосов
/ 30 апреля 2010

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

Кстати, функция не будет продолжать обрабатывать файл, она будет запускаться только тогда, когда вы его вызываете, поэтому это может быть не то, что вам нужно.

(Это псевдокод, похожий на javascript)

function fileReader (filename) {
    var  file = open(filename);

    return function () {
        while (s = file.read()) {
            if (condition) {
                return line;
            }
        }
        return null;
   }     
}

a = fileReader("myfile");
line1 = a();
line2 = a();
line3 = a();
0 голосов
/ 30 апреля 2010

Если вы действительно хотите это сделать, вы можете использовать потоки. Одним из вариантов может быть разветвление отдельного потока, который читает файл, и когда он находит определенную строку, поместите его в массив, который разделяется между потоками. Тогда другой поток может взять строки, как они найдены, и обработать их. Вот пример, который читает файл, ищет «X» в строке файла и выполняет действие, когда он найден.

use strict;
use threads;
use threads::shared;

my @ary : shared;

my $thr = threads->create('file_reader');

while(1){
    my ($value);
    {
        lock(@ary);
        if ($#ary > -1){
            $value = shift(@ary);
            print "Found a line to process:  $value\n";
        }
        else{
            print "no more lines to process...\n";
        }            
    }

    sleep(1);
    #process $value
}


sub file_reader{

            #File input
    open(INPUT, "<test.txt");
    while(<INPUT>){
        my($line) = $_;
        chomp($line);

        print "reading $line\n";

        if ($line =~ /X/){
            print "pushing $line\n";
            lock(@ary);
            push @ary, $line;
        }
        sleep(4)
    }
    close(INPUT);
}

Попробуйте этот код как файл test.txt:

line 1
line 2X
line 3
line 4X
line 5
line 6
line 7X
line 8
line 9
line 10
line 11
line 12X
...