Spec-Zone.ru › Perl 5.28

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

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

Автор

Mark-Jason Dominus (mjd-perl-memoize+@plover.com), компания Plover Systems co.

См. страницу 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 сам по себе.

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

Большое спасибо Florian Ragwitz за помощь в администрировании и упаковке, John Tromp за сообщения об ошибках, Jonathan Roy за сообщения об ошибках и предложения, Michael Schwern за другие сообщения об ошибках и исправления, Mike Cariaso за помощь в определении правильного подхода к истечению срока действия, Joshua Gerth, Joshua Chamas, Jonathan Roy (еще раз), Mark D. Anderson и Andrew Johnson за дополнительные предложения по истечению срока действия, Brent Powers за модуль Memoize::ExpireLRU, Ariel Scolnicov за замечательные сообщения о функции Фибоначчи, Dion Almaer за стимулирующие предложения по функции нормализации по умолчанию, Walt Mankowski и Kurt Starsinic за большую помощь в расследовании проблем в многопоточном Perl, Alex Dudkevich за сообщение об ошибке в прототипированных функциях и проверку моего исправления, Tony Bass за много полезных предложений, Jonathan Roy (еще раз) за нахождение применения для unmemoize(), Philippe Verdret за содержательные обсуждения Hook::PrePostCall, Nat Torkington за советы, которые я проигнорировал, Chris Nandor за советы по переносимости, Randal Schwartz за предложение функции `flush_cache` и Jenda Krynicky за то, что он свет в этом мире.

Особая благодарность Jarkko Hietaniemi, кузнецу 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.28.3/Memoize

Spec-Zone.ru

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