Spec-Zone.ru › Perl 5.32

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 или управляющим символом \.) Это всегда работает правильно для функций с одним строковым аргументом и также, когда аргументы никогда не содержат символ 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-ю строку файла данных; в 15: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 позволяет указать, что для кэширования будет использоваться конкретный хэш, который вы предоставите. Вы можете предварительно привязать этот хэш, чтобы придать ему любое необходимое поведение.

Привязанный хэш может иметь любое семантическое поведение. Обычно он привязан к базе данных на диске, поэтому кэшированные значения хранятся в базе данных и извлекаются из неё при необходимости, а файл на диске обычно сохраняется после завершения вашей программы. См. 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 — предоставить собственную переменную хэша. Вы сможете затем просматривать или изменять содержимое хэша для более точного управления управлением кэшем.

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, включённой в этот пакет.

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

Альтернативный подход к очистке кэша — использовать параметр HASH (см. выше), чтобы попросить Memoize использовать определённую переменную хэша в качестве кэша. Затем вы можете в любое время просмотреть или изменить хэш любым желаемым способом. Вы можете очистить кэш, используя %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 записывалась в `сетевом порядке`. (См. 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.

Автор

Mark-Jason Dominus (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 года Mark Jason Dominus

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

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

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

Особая благодарность Яркко Хиетаниеми, создателю 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.32.0/Memoize

Spec-Zone.ru

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