Следующее упражнение взято из с. 234 Программирование Иерусалимского в Lua (4-е издание). ( NB: Ранее в книге автор явно отвергает слово памятка и настаивает на использовании вместо этого запоминание . Имейте это в виду, читая отрывок ниже.)
Упражнение 23.3: Представьте, что вам нужно реализовать таблицу запоминания для функции от строк к строкам. Создание слабой таблицы не приведет к удалению записей, поскольку слабые таблицы не рассматривают строки как собираемые объекты. Как вы можете реализовать запоминание в этом случае?
Я в тупике!
Часть моей проблемы заключается в том, что я не смог придумать способ вызвать (мусорный) сбор строки.
В отличие от таблицы, я могу оснастить ее финализатором, который сообщит, когда таблица собирается быть собранной. Есть ли способ подтвердить, что данная строка (и только эта строка) была собрана сборщиком мусора?
Другая сложность заключается в простом определении спецификации требуемой функции . Лучшее, что я могу сделать, это выяснить, что это не так. Ранее в книге (стр. 225) автор привел следующий пример функции «запоминания»:
Представьте себе универсальный сервер, который принимает запросы в виде строк с кодом Lua. Каждый раз, когда он получает запрос, он запускает load
в строке и затем вызывает результирующую функцию. Однако load
- дорогостоящая функция, и некоторые команды серверу могут быть довольно частыми. Вместо многократного вызова load
каждый раз, когда он получает общую команду, такую как "closeconnection()"
, сервер может запомнить результаты из load
, используя вспомогательную таблицу. Перед вызовом load
сервер проверяет в таблице, имеет ли данная строка уже перевод. Если он не может найти соответствие, то (и только тогда) сервер вызывает load
и сохраняет результат в таблице. Мы можем упаковать это поведение в новую функцию:
[стандартная памятка (пропущенная) реализация опущена; см. вариант с использованием таблицы слабых значений ниже]
Экономия при такой схеме может быть огромной. Однако это также может привести к непредвиденным отходам. Хотя некоторые команды повторяются снова и снова, многие другие команды выполняются только один раз. Постепенно таблица [«запоминания»] results
накапливает все команды, которые сервер когда-либо получал, плюс их соответствующие коды; через некоторое время это приведет к исчерпанию памяти сервера.
Слабая таблица обеспечивает простое решение этой проблемы. Если таблица results
имеет слабые значения, каждый цикл сбора мусора удаляет все неиспользуемые в данный момент переводы (что означает практически все из них) 1 :
local results = {}
setmetatable(results, {__mode = "v"}) -- make values weak
function mem_loadstring (s)
local res = results[s]
if res == nil then -- results not available?
res = assert(load(s)) -- compute new results
result[s] = res -- save for later reuse
end
return res
end
Как отмечается в исходном заявлении о проблеме, эта схема не будет работать, когда функция, подлежащая запоминанию, возвращает строки, потому что сборщик мусора не рассматривает строки как "коллекционные".
Конечно, если разрешено изменять интерфейс нужной функции, чтобы вместо возврата строки она возвращала одноэлементную таблицу, единственным элементом которой является строка результата real , тогда проблема становится почти тривиальной , но мне трудно поверить, что автор имел в виду такое грубое «решение» 2 .
В случае, если это имеет значение, я использую Lua 5.3.
1 Кроме того, если обоснование для памятки (r) заключается в том, чтобы избегать вызова load
чаще, чем необходимо, схема, предложенная автором, не имеет смысла для меня , Мне кажется, что эта схема основана на предположении (на самом деле эвристическом), что перевод, который часто используется и, следовательно, платит за памятку (r) ize, также всегда доступен (и, следовательно, не подлежит сбору) , Я не понимаю, почему это обязательно или даже может иметь место.
2 На эту свинью можно нанести помаду в форме __tostring
метода, который позволил бы использовать таблицу (тот, который был возвращен функцией memo (r) ized)маскироваться под строку в определенных контекстах;это все еще свинья.