Spec-Zone.ru › Perl 5.30

Memoize

СОДЕРЖАНИЕ

  • ИМЯ
  • СИНОПСИС
  • ОПИСАНИЕ
  • ДЕТАЛИ
  • НАСТРОЙКИ
    • УСТАНОВКА
    • НОРМАЛИЗАТОР
    • SCALAR_CACHE, LIST_CACHE
      • Значения списков в скалярном контексте
      • Объединённые кэши на диске
  • ДРУГИЕ ВОЗМОЖНОСТИ
    • unmemoize
    • flush_cache
  • ОГРАНИЧЕНИЯ
  • ПОДДЕРЖКА ПОСТОЯННОГО КЭША
  • ПОДДЕРЖКА ИСТЕЧЕНИЯ СРОКА ДЕЙСТВИЯ
  • ОШИБКИ
  • СПИСОК РАССЫЛКИ
  • АВТОР
  • АВТОРСКИЕ ПРАВА И ЛИЦЕНЗИЯ
  • БЛАГОДАРНОСТИ

ИМЯ

Memoize — ускорение функций за счёт обмена памяти на время

СИНОПСИС

# This is the documentation for Memoize 1.03
use Memoize;
memoize('slow_function');
slow_function(arguments);    # Is faster than it was before

Это обычно всё, что вам нужно знать. Однако доступно множество настроек:

memoize(function, options...);

Настройки включают:

NORMALIZER => function
INSTALL => new_name

SCALAR_CACHE => 'MEMORY'
SCALAR_CACHE => ['HASH', \%cache_hash ]
SCALAR_CACHE => 'FAULT'
SCALAR_CACHE => 'MERGE'

LIST_CACHE => 'MEMORY'
LIST_CACHE => ['HASH', \%cache_hash ]
LIST_CACHE => 'FAULT'
LIST_CACHE => 'MERGE'

ОПИСАНИЕ

«Запоминание» функции ускоряет её за счёт обмена памяти на время. Это делается путём кеширования возвращаемых значений функции в таблице. Если вы вызываете функцию снова с теми же аргументами, memoize и выдаёт значение из таблицы, вместо того, чтобы позволить функции вычислять значение заново.

Вот экстремальный пример. Рассмотрим последовательность Фибоначчи, определённую следующей функцией:

# Compute Fibonacci numbers
sub fib {
  my $n = shift;
  return $n if $n < 2;
  fib($n-1) + fib($n-2);
}

Эта функция очень медленная. Почему? Чтобы вычислить fib(14), она сначала хочет вычислить fib(13) и fib(12), а затем сложить результаты. Но чтобы вычислить fib(13), она сначала должна вычислить fib(12) и fib(11), а затем возвращается и вычисляет fib(12) снова, хотя ответ тот же. И оба раза, когда она хочет вычислить fib(12), она должна вычислить fib(11) с нуля, а затем сделать это снова каждый раз, когда она хочет вычислить fib(13). Эта функция выполняет столько перевычислений старых результатов, что её выполнение занимает очень много времени — fib(14) делает 1200 дополнительных рекурсивных вызовов к самой себе, чтобы вычислить и перевычислить вещи, которые она уже вычислила.

Эта функция хорошо подходит для запоминания. Если вы запоминаете функцию `fib` выше, она вычислит fib(14) ровно один раз, в первый раз, когда ей это нужно, а затем сохранит результат в таблице. Затем, если вы запросите fib(14) снова, она выдаст результат из таблицы. Во время вычисления fib(14), вместо вычисления fib(12) дважды, она делает это один раз; второй раз, когда ей нужно значение, она получает его из таблицы. Она не вычисляет fib(11) четыре раза; она вычисляет её один раз, получая её из таблицы в следующие три раза. Вместо того, чтобы делать 1200 рекурсивных вызовов `fib`, она делает 15. Это делает функцию примерно в 150 раз быстрее.

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

# Compute Fibonacci numbers, memoized version
{ my @fib;
  sub fib {
    my $n = shift;
    return $fib[$n] if defined $fib[$n];
    return $fib[$n] = $n if $n < 2;
    $fib[$n] = fib($n-1) + fib($n-2);
  }
}

Или вы могли бы использовать этот модуль, например так:

use Memoize;
memoize('fib');

# Rest of the fib function just like the original version.

Это упрощает включение и выключение запоминания.

Вот ещё один более простой пример: я написал простой трейсер лучей; программа смотрела в определённом направлении, определяла, на что она смотрит, а затем преобразовывала значение `color` (обычно строку, например, `red`) этого объекта в значение красного, зелёного и синего пикселя, например так:

for ($direction = 0; $direction < 300; $direction++) {
  # Figure out which object is in direction $direction
  $color = $object->{color};
  ($r, $g, $b) = @{&ColorToRGB($color)};
  ...
}

Поскольку в изображении относительно немного объектов, существует только несколько цветов, которые многократно просматриваются. Запоминание ColorToRGB ускорило программу на несколько процентов.

ДЕТАЛИ

Этот модуль экспортирует ровно одну функцию, memoize. Остальные функции в этом пакете — не ваше дело.

Вы должны сказать

memoize(function)

где function — имя функции, которую вы хотите запомнить, или ссылка на неё. memoize возвращает ссылку на новую, запомненную версию функции или undef при некритичной ошибке. В настоящее время некритических ошибок нет, но в будущем они могут появиться.

Если function — имя функции, то memoize скрывает старую версию и устанавливает новую запомненную версию под старым именем, так что &function(...) фактически вызывает запомненную версию.

НАСТРОЙКИ

Существует несколько необязательных настроек, которые вы можете передать memoize для изменения её поведения. Чтобы указать настройки, вызовите memoize так:

memoize(function, NORMALIZER => function,
                  INSTALL => newname,
                  SCALAR_CACHE => option,
                  LIST_CACHE => option
                 );

Каждая из этих настроек необязательна; вы можете включить некоторые, все или ни одной.

УСТАНОВКА

Если вы передаёте имя функции INSTALL, memoize установит новую запомненную версию функции под заданным именем. Например,

memoize('fib', INSTALL => 'fastfib')

устанавливает запомненную версию fib как fastfib; без опции INSTALL она бы заменила старую fib запомненной версией.

Чтобы предотвратить memoize от установки запомненной версии где-либо, используйте INSTALL => undef.

НОРМАЛИЗАТОР

Предположим, ваша функция выглядит так:

# Typical call: f('aha!', A => 11, B => 12);
sub f {
  my $a = shift;
  my %hash = @_;
  $hash{B} ||= 2;  # B defaults to 2
  $hash{C} ||= 7;  # C defaults to 7

  # Do something with $a, %hash
}

Теперь следующие вызовы вашей функции полностью эквивалентны:

f(OUCH);
f(OUCH, B => 2);
f(OUCH, C => 7);
f(OUCH, B => 2, C => 7);
f(OUCH, C => 7, B => 2);
(etc.)

Однако, если вы не сообщите Memoize, что эти вызовы эквивалентны, она не будет знать об этом и будет вычислять значения для этих вызовов вашей функции отдельно и хранить их отдельно.

Чтобы предотвратить это, предоставьте функцию NORMALIZER, которая преобразует аргументы программы в строку таким образом, что эквивалентные аргументы превращаются в одну и ту же строку. Функция NORMALIZER для f выше может выглядеть так:

sub normalize_f {
  my $a = shift;
  my %hash = @_;
  $hash{B} ||= 2;
  $hash{C} ||= 7;

  join(',', $a, map ($_ => $hash{$_}) sort keys %hash);
}

Каждый из списков аргументов выходит из функции normalize_f точно так же, например так:

OUCH,B,2,C,7

Вы бы сказали Memoize использовать этот нормализатор так:

memoize('f', NORMALIZER => 'normalize_f');

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

По умолчанию нормализатор просто конкатенирует аргументы с символом 28 между ними. (В ASCII это называется FS или control-\.) Это всегда работает правильно для функций с одним строковым аргументом, а также когда аргументы никогда не содержат символ 28. Однако это может сбить с толку некоторые списки аргументов:

normalizer("a\034", "b")
normalizer("a", "\034b")
normalizer("a\034\034b")

например.

Поскольку ключи хэшей являются строками, нормализатор по умолчанию не будет различать undef и пустую строку. Он также не будет работать, когда аргументы функции являются ссылками. Например, рассмотрим функцию g, которая получает два аргумента: число и ссылку на массив чисел:

g(13, [1,2,3,4,5,6,7]);

По умолчанию нормализатор преобразует это во что-то вроде "13\034ARRAY(0x436c1f)". Это было бы нормально, за исключением того, что последующий массив чисел может быть сохранён в другом месте, даже если он содержит те же данные. Если это произойдёт, Memoize будет считать, что аргументы отличаются, хотя они эквивалентны. В этом случае подходит такой нормализатор:

sub normalize { join ' ', $_[0], @{$_[1]} }

Для примера выше это создаёт ключ «13 1 2 3 4 5 6 7».

Другое применение нормализаторов — когда функция зависит от данных, отличных от тех, что находятся в её аргументах. Предположим, у вас есть функция, которая возвращает значение, зависящее от текущего часа дня:

sub on_duty {
  my ($problem_type) = @_;
  my $hour = (localtime)[2];
  open my $fh, "$DIR/$problem_type" or die...;
  my $line;
  while ($hour-- > 0)
    $line = <$fh>;
  } 
  return $line;
}

В 10:23 эта функция генерирует 10-ю строку файла данных; в 3:45 дня она генерирует 15-ю строку вместо этого. По умолчанию Memoize будет видеть только аргумент $problem_type. Чтобы исправить это, включите текущий час в нормализатор:

sub normalize { join ' ', (localtime)[2], @_ }

Контекст вызова функции (скалярный или списочный) передаётся нормализатору. Это означает, что если запомненная функция будет обрабатывать свои аргументы по-разному в списочном контексте, чем в скалярном, вы можете сделать так, чтобы функция нормализации выбирала своё поведение на основе результатов wantarray. Даже если вызов выполнен в списочном контексте, нормализатор всё равно должен вернуть одну строку.

SCALAR_CACHE, LIST_CACHE

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

Внутри Memoize есть небольшая сложность: фактически существует два кэша, один для скалярных значений и один для значений списков. Когда ваша функция вызывается в скалярном контексте, её возвращаемое значение кешируется в одном хэше, а когда ваша функция вызывается в списочном контексте, её значение кешируется в другом хэше. Вы можете контролировать поведение кеширования обоих контекстов независимо с помощью этих опций.

Аргумент LIST_CACHE или SCALAR_CACHE должен быть одной из следующих четырёх строк:

MEMORY
FAULT
MERGE
HASH

или он должен быть ссылкой на массив, первым элементом которого является одна из этих четырёх строк, например [HASH, arguments...].

MEMORY

MEMORY означает, что возвращаемые значения из функции будут кэшироваться в обычной переменной Perl hash. Переменная hash не будет сохраняться после завершения программы. Это значение по умолчанию.

HASH

HASH позволяет указать, что конкретный hash, который вы предоставите, будет использоваться в качестве кэша. Вы можете связать этот hash предварительно, чтобы придать ему любое нужное поведение.

Связанный hash может иметь любое семантическое значение. Обычно он связан с базой данных на диске, так что кэшированные значения хранятся в базе данных и извлекаются из нее при необходимости, а файл на диске обычно сохраняется после завершения вашей программы. Смотрите perltie для более подробных сведений о tie.

Типичный пример:

use DB_File;
tie my %cache => 'DB_File', $filename, O_RDWR|O_CREAT, 0666;
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

Это приводит к хранению кэша в базе данных DB_File, имя которой находится в $filename. Кэш будет сохранен после завершения программы. В следующий раз, когда программа будет запущена, она найдет кэш, уже заполненный из предыдущего запуска программы. Или вы можете принудительно заполнить кэш, создав пакетную программу, которая выполняется в фоновом режиме и заполняет файл кэша. Затем, когда вы запустите свою реальную программу, мемоизированная функция будет быстрой, потому что все ее результаты были предварительно вычислены.

Еще одна причина для использования HASH — предоставить свою собственную переменную hash. Вы можете затем просмотреть или изменить содержимое hash, чтобы получить более тонкий контроль над управлением кэшем.

TIE

Этот параметр больше не поддерживается. Он по-прежнему документирован только для помощи в отладке старых программ, которые его используют. Старые программы следует преобразовать в использование параметра HASH вместо этого.

memoize ... ['TIE', PACKAGE, ARGS...]

является всего лишь сокращением для

require PACKAGE;
{ tie my %cache, PACKAGE, ARGS...;
  memoize ... [HASH => \%cache];
}
FAULT

FAULT означает, что вы никогда не ожидаете вызывать функцию в скалярном (или списочном) контексте, и что если Memoize обнаружит такой вызов, то она должна прервать выполнение программы. Сообщение об ошибке — одно из

`foo' function called in forbidden list context at line ...
`foo' function called in forbidden scalar context at line ...
MERGE

MERGE обычно означает, что мемоизированная функция не различает списочный и скалярный контексты и что возвращаемые значения в обоих контекстах должны храниться вместе. И LIST_CACHE => MERGE, и SCALAR_CACHE => MERGE означают одно и то же.

Рассмотрим эту функцию:

sub complicated {
  # ... time-consuming calculation of $result
  return $result;
}

Функция complicated вернет то же числовое $result независимо от того, вызывается ли она в списочном или скалярном контексте.

Обычно следующий код приведет к двум вызовам complicated, даже если complicated мемоизирована:

$x = complicated(142);
($y) = complicated(142);
$z = complicated(142);

Первый вызов кэширует результат, скажем, 37, в скалярном кэше; второй — список (37) в списочном кэше. Третий вызов не вызывает реальную функцию complicated; он получает значение 37 из скалярного кэша.

Очевидно, второй вызов complicated — пустая трата времени, а хранение его возвращаемого значения — пустая трата места. Указание LIST_CACHE => MERGE заставит memoize использовать один и тот же кэш для возвращаемых значений в скалярном и списочном контекстах, так что второй вызов будет использовать скалярный кэш, который был заполнен первым вызовом. complicated вызывается только один раз, и оба последующих вызова возвращают 3 из кэша, независимо от контекста вызова.

Список значений в скалярном контексте

Рассмотрим эту функцию:

sub iota { return reverse (1..$_[0]) }

Эта функция обычно возвращает список. Предположим, что вы мемоизировали ее и объединили кэши:

memoize 'iota', SCALAR_CACHE => 'MERGE';

@i7 = iota(7);
$i7 = iota(7);

Здесь первый вызов кэширует список (1,2,3,4,5,6,7). Второй вызов не имеет реального смысла. Memoize не может догадаться, какое поведение должно иметь iota в скалярном контексте, не вызвав его в скалярном контексте. Обычно Memoize будет вызывать iota в скалярном контексте и кэшировать результат, но параметр SCALAR_CACHE => 'MERGE' говорит об этом не делать, а вместо этого использовать значение кэшированного списка в контексте списка. Но он не может вернуть список из семи элементов в скалярном контексте. В этом случае $i7 получит первый элемент кэшированного значения списка, а именно 7.

Объединенные кэши на диске

Еще одно применение MERGE — когда вы хотите хранить оба типа возвращаемых значений в одном файле на диске; это избавит вас от необходимости иметь дело с двумя файлами на диске вместо одного. Вы можете использовать функцию нормализации, чтобы сохранить два набора возвращаемых значений раздельно. Например:

tie my %cache => 'MLDBM', 'DB_File', $filename, ...;

memoize 'myfunc',
  NORMALIZER => 'n',
  SCALAR_CACHE => [HASH => \%cache],
  LIST_CACHE => 'MERGE',
;

sub n {
  my $context = wantarray() ? 'L' : 'S';
  # ... now compute the hash key from the arguments ...
  $hashkey = "$context:$hashkey";
}

Эта функция нормализации будет хранить возвращаемые значения скалярного контекста в файле на диске под ключами, начинающимися с S:, а возвращаемые значения списочного контекста — под ключами, начинающимися с L:.

ДРУГИЕ ВОЗМОЖНОСТИ

unmemoize

Существует функция unmemoize, которую вы можете импортировать, если хотите. Зачем это нужно? Вот пример: Предположим, что ваш кэш связан с файлом DBM, и вы хотите убедиться, что кэш записывается на диск, если кто-то прерывает программу. Если программа завершается нормально, это произойдет в любом случае, но если кто-то нажмет Ctrl+C или что-то подобное, то программа завершится немедленно без синхронизации базы данных. Итак, что вы можете сделать вместо этого

$SIG{INT} = sub { unmemoize 'function' };

unmemoize принимает ссылку на или имя ранее мемоизированной функции и отменяет все, что было сделано для предоставления мемоизированной версии в первую очередь, включая присвоение имени немемоизированной версии, если это необходимо. Она возвращает ссылку на немемоизированную версию функции.

Если вы попросите ее размемоизировать функцию, которая никогда не была мемоизирована, она выдаст ошибку.

flush_cache

flush_cache(function) очистит кэши, удалив все кэшированные данные. Аргумент может быть именем функции или ссылкой на функцию. Для более точного управления тем, когда данные удаляются или истекают, см. документацию по Memoize::Expire, включенную в этот пакет.

Обратите внимание, что если кэш — это связанный hash, flush_cache попытается вызвать метод CLEAR в hash. Если метод CLEAR отсутствует, это приведет к ошибке во время выполнения.

Альтернативный подход к очистке кэша — использование параметра HASH (см. выше), чтобы попросить Memoize использовать конкретную переменную hash в качестве своего кэша. Затем вы можете в любое время и любым желаемым способом просмотреть или изменить hash. Вы можете очистить кэш, используя %hash = ().

ОСОБЕННОСТИ

Мемоизация не является панацеей:

  • Не мемоизируйте функцию, поведение которой зависит от состояния программы, отличного от ее собственных аргументов, например, от глобальных переменных, времени суток или входных данных файла. Эти функции не будут давать правильных результатов при мемоизации. Вот особенно простой пример:

    sub f {
      time;
    }

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

  • Не мемоизируйте функцию со побочными эффектами.

    sub f {
      my ($a, $b) = @_;
      my $s = $a + $b;
      print "$a + $b = $s.\n";
    }

    Эта функция принимает два аргумента, складывает их и выводит их сумму. Ее возвращаемое значение — количество напечатанных символов, но вас, вероятно, это не интересовало. Но Memoize этого не понимает. Если вы мемоизируете эту функцию, вы получите ожидаемый результат в первый раз, когда попросите ее вывести сумму 2 и 3, но последующие вызовы вернут 1 (возвращаемое значение print) без фактического вывода чего-либо.

  • Не мемоизируйте функцию, которая возвращает структуру данных, которая изменяется вызывающим ее кодом.

    Рассмотрим эти функции: getusers возвращает список пользователей каким-то образом, а затем main выбрасывает первого пользователя из списка и выводит остальных:

    sub main {
      my $userlist = getusers();
      shift @$userlist;
      foreach $u (@$userlist) {
        print "User $u\n";
      }
    }
    
    sub getusers {
      my @users;
      # Do something to get a list of users;
      \@users;  # Return reference to list.
    }

    Если вы мемоизируете getusers здесь, она будет работать правильно ровно один раз. Ссылка на список пользователей будет храниться в таблице мемо. main удалит первый элемент из ссылающегося списка. В следующий раз, когда вы вызовете main, Memoize не вызовет getusers; она просто вернет ту же ссылку на тот же список, который она получила в прошлый раз. Но в этот раз список уже потерял свою голову; main ошибочно удалит еще один элемент из него. Список будет становиться короче с каждым вызовом main.

    Аналогично, это:

    $u1 = getusers();    
    $u2 = getusers();    
    pop @$u1;

    также изменит $u2, как и $u1, потому что обе переменные ссылаются на один и тот же массив. Если бы getusers не была мемоизирована, $u1 и $u2 ссылались бы на разные массивы.

  • Не мемоизируйте очень простую функцию.

    Недавно кто-то сказал мне, что модуль Memoize замедлил его программу вместо ускорения. Оказалось, что он мемоизировал следующую функцию:

    sub square {
      $_[0] * $_[0];
    }

    Я указал, что Memoize использует хеш, и что поиск числа в хеше обязательно займет гораздо больше времени, чем однократное умножение. Действительно, нет способа ускорить функцию square.

    Мемоизация — не магия.

Поддержка кэша с постоянным хранением

Вы можете связать таблицы кэша с любым типом связанного хеша, который вам нужен, при условии, что он поддерживает TIEHASH, FETCH, STORE, и EXISTS. Например,

tie my %cache => 'GDBM_File', $filename, O_RDWR|O_CREAT, 0666;
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

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

SDBM_File не предоставляет метод EXISTS, поэтому в этот пакет включен модуль склейки под названием Memoize::SDBM_File, который его предоставляет. Используйте его вместо простого SDBM_File для хранения таблицы кэша на диске в базе данных SDBM_File:

tie my %cache => 'Memoize::SDBM_File', $filename, O_RDWR|O_CREAT, 0666;
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

NDBM_File имеет ту же проблему и то же решение. (Используйте Memoize::NDBM_File instead of plain NDBM_File.)

Storable — это вообще не класс связанного хеша. Вы можете использовать его для хранения хеша на диске и повторного извлечения его, но вы не можете изменять хеш, пока он находится на диске. Таким образом, если вы хотите хранить таблицу кэша в базе данных Storable, используйте Memoize::Storable, который помещает интерфейс, подобный хешу, на Storable. Таблица хешей фактически хранится в памяти и загружается из вашего файла Storable в момент мемоизации функции и сохраняется обратно в момент размемоизации функции (или при завершении программы):

tie my %cache => 'Memoize::Storable', $filename;
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

tie my %cache => 'Memoize::Storable', $filename, 'nstore';
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

Включите опцию `nstore`, чтобы база данных Storable записывалась в формате `network order`. (См. Storable для получения дополнительной информации об этом.)

Функция flush_cache() вызовет ошибку во время выполнения, если связанный пакет не предоставляет метод CLEAR.

Поддержка истечения срока действия

См. Memoize::Expire, модуль-плагин, добавляющий функциональность истечения срока действия к Memoize. Если вам не нравятся типы политик, реализуемых Memoize::Expire, вы можете легко написать свой собственный модуль-плагин для реализации любой желаемой политики. В Memoize есть несколько примеров. Управляющий истечением срока действия, реализующий политику LRU, доступен в CPAN как Memoize::ExpireLRU.

Ошибки

Тестовый набор значительно улучшился, но всегда нуждается в доработке.

Существует проблема с тем, как работает goto &f в многопоточном Perl, возможно, из-за лексического ограничения @_. Это ошибка в Perl, и до ее устранения кешированные функции будут видеть немного отличающийся caller() и работать немного медленнее в многопоточных Perl, чем в однопоточных.

В некоторых версиях DB_File нельзя хранить данные под ключом длиной 0. Это означает, что если у вас есть функция f, которую вы кешируете, и кеш находится в базе данных DB_File, то значение f() (f вызванное без аргументов) не будет кешировано. Если это большая проблема, вы можете предоставить функцию нормализации, которая добавляет "x" к каждому ключу.

Список рассылки

Чтобы присоединиться к списку рассылки с очень низким объемом трафика об объявлениях о Memoize, отправьте пустое сообщение на mjd-perl-memoize-request@plover.com.

Автор

Марк-Джейсон Доминик (mjd-perl-memoize+@plover.com), компания Plover Systems.

Смотрите страницу Memoize.pm по адресу http://perl.plover.com/Memoize/ для новостей и обновлений. Недалеко от этой страницы, по адресу http://perl.plover.com/MiniMemoize/, есть статья о мемоизации и внутреннем устройстве Memoize, опубликованная в выпуске журнала The Perl Journal #13. (Эта статья также включена в дистрибутив Memoize как `article.html`.)

В книге автора Higher-Order Perl (2005, ISBN 1558607013, издательство Morgan Kaufmann) подробно рассматривается мемоизация (и многие другие темы). Она доступна онлайн бесплатно. Для получения дополнительной информации посетите http://hop.perl.plover.com/.

Чтобы присоединиться к списку рассылки о новостях о Memoize, отправьте пустое сообщение на mjd-perl-memoize-request@plover.com. Этот список рассылки предназначен только для объявлений и имеет очень низкий объем трафика — меньше двух сообщений в год.

Авторские права и лицензия

Авторские права 1998, 1999, 2000, 2001, 2012 года Марка Джейсона Доминика

Эта библиотека является свободно распространяемым программным обеспечением; вы можете перераспределять и/или изменять его в соответствии с теми же условиями, что и сам Perl.

Благодарности

Большое спасибо Флориану Рагвицу за помощь в администрировании и упаковке, Джону Тромпу за сообщения об ошибках, Джонатану Рою за сообщения об ошибках и предложения, Майклу Шверну за другие сообщения об ошибках и исправления, Майку Кариасо за помощь в определении правильного решения по истечению срока действия, Джошуа Герту, Джошуа Чамасу, Джонатану Рою (снова), Марку Д. Андерсону и Эндрю Джонсону за дополнительные предложения об истечении срока действия, Бренту Пауэрсу за модуль Memoize::ExpireLRU, Ариэлю Сколникову за приятные сообщения о функции Фибоначчи, Диону Альмаеру за стимулирующие предложения о функции нормализации по умолчанию, Уолту Манковскому и Курту Стасинику за значительную помощь в расследовании проблем в многопоточном Perl, Алексу Дудкевичу за сообщение об ошибке в прототипированных функциях и за проверку моего исправления, Тони Бассу за множество полезных предложений, Джонатану Рою (снова) за нахождение применения для unmemoize(), Филиппу Вердре за содержательные обсуждения Hook::PrePostCall, Нату Торкинтону за советы, которые я проигнорировал, Крису Нандору за советы по переносимости, Рэндалу Шварцу за предложение функции `flush_cache`, и Женде Крывницкому за то, что он свет в этом мире.

Особая благодарность Ярккo Хьетаниеми, автору версии 5.8.0, за включение этого модуля в ядро и за его терпеливое и полезное руководство во время процесса интеграции.

2 Ошибки POD

При разборе POD были обнаружены следующие ошибки:

Около строки 755:

Вы забыли `=back` перед `=head3`

Около строки 804:

`=back` без `=over`

© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.30.3/Memoize

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API