Я знаю, что memoization, кажется, является постоянной проблемой здесь, в теге haskell при переполнении стека, но я думаю, что этот вопрос не задавался раньше.
Я знаю несколько разных библиотек memoization для "Hackell":
- Компоненты memo-combinators и memotrie, которые используют красивый трюк с ленивыми бесконечными структурами данных для достижения memoization чисто функциональным способом. (Как я понимаю, первый немного более гибкий, в то время как последний проще использовать в простых случаях: см. этот SO ответ для обсуждения.)
- Пакет uglymemo, который использует небезопасныйPerformIO внутри, но все же представляет собой ссылочно прозрачный интерфейс. Использование unsafePerformIO внутренне приводит к лучшей производительности, чем предыдущие два пакета. (Вне полки его реализация использует структуры данных поиска на основе сравнения, а не, возможно, несколько более эффективные хэш-функции, но я думаю, что если вы найдете и замените
Cmp
дляHashable
иData.Map
дляData.HashMap
и добавьте Appropraiteimport
s, вы получите версию на основе хэша.)
Однако я не знаю ни одной библиотеки, которая будет отвечать на запросы, основанные на идентификаторе объекта, а не на значении объекта. Это может быть важно, потому что иногда типы объектов, которые используются в качестве ключей к вашей таблице заметок (то есть, как ввод функции, которая сохраняется в память), могут быть большими - настолько большими, чтобы полностью изучить объект, чтобы определить, "Я видел это раньше, это сама медленная операция. Медленный, а также ненужный, если вы снова и снова будете применять memoized функцию к объекту, который хранится в данном" месте в памяти "1. (Это может произойти, например, если мы memoizing функцию, которая называется рекурсивно над некоторой большой структурой данных с большим количеством структурного обмена.) Если мы уже вычислили нашу memoized функцию на этом конкретном объекте раньше, мы можем уже знаю ответ, даже не глядя на сам объект!
Реализация такой библиотеки memoization включает в себя несколько тонких проблем, и для правильного выполнения этого требуется несколько специальных частей поддержки от языка. К счастью, GHC предоставляет все специальные функции, которые нам нужны, и есть бумага от Пейтона-Джонса, Марлоу и Эллиота, которые в основном беспокоят большинство этих вопросов для вас, объясняя, как построить прочную реализацию. Они не предоставляют всех подробностей, но они приближаются.
Одна деталь, которую я могу видеть, о которой, вероятно, следует беспокоиться, но о которой они не беспокоятся, - это безопасность потоков. Их код, по-видимому, не является нитевидным.
Мой вопрос: кто-нибудь знает о упакованной библиотеке, которая делает вид memoization, обсуждаемый в бумаге Peyton-Jones, Marlow и Elliott, заполняя все детали (и предпочтительно заполняя надлежащую безопасность потоков)?
В противном случае, я предполагаю, что мне придется сам его закодировать: есть ли у кого-нибудь идеи других тонкостей (помимо безопасности потоков и тех, которые обсуждались в документе), которые разработчик такой библиотеки мог бы хорошо переносить ум?
UPDATE
Следующее предложение @luqui ниже, здесь немного больше данных о конкретной проблеме, с которой я сталкиваюсь. Предположим, что существует тип:
data Node = Node [Node] [Annotation]
Этот тип может использоваться для представления простого типа корневой DAG в памяти, где Node
являются узлами DAG, корень - это только выделенный Node
, и каждый node аннотируется с некоторым Annotation
внутренняя структура, я думаю, не должна беспокоить нас (но если это имеет значение, просто спросите, и я буду более конкретным.) Если использовать таким образом, обратите внимание, что в памяти может быть значительный структурный обмен между Node
в памяти - - может быть экспоненциально больше путей, которые приводят от корня к node, чем сами узлы. Мне дана структура данных этой формы из внешней библиотеки, с которой я должен взаимодействовать; Я не могу изменить тип данных.
У меня есть функция
myTransform : Node -> Node
детали, которые нас не интересуют (или, по крайней мере, я так думаю, но опять же я могу быть более конкретным, если это необходимо). Он сопоставляет узлы узлам, исследуя аннотации node, и аннотации его непосредственных детей, чтобы придумать новый Node
с теми же детьми, но, возможно, разные аннотации. Я хочу написать функцию
recursiveTransform : Node -> Node
результат которого выглядит так же, как структура данных, как вы бы это сделали:
recursiveTransform Node originalChildren annotations =
myTransform Node recursivelyTransformedChildren annotations
where
recursivelyTransformedChildren = map recursiveTransform originalChildren
за исключением того, что он использует структурное разделение очевидным образом, чтобы он не возвращал экспоненциальную структуру данных, а скорее один из того же размера, что и его ввод.
Я понимаю, что все это было бы проще, если бы сказали, что Nodes
были пронумерованы до того, как я их получил, иначе я мог бы изменить определение Node
. Я не могу (легко) сделать любую из этих вещей.
Меня также интересует общий вопрос о существовании библиотеки, реализующей функциональность, о которой я упоминаю, совершенно независимо от конкретной конкретной проблемы, с которой я сталкиваюсь прямо сейчас: я чувствую, что мне пришлось работать над такой проблемой на несколько раз, и было бы неплохо убить дракона раз и навсегда. Тот факт, что SPJ и соавт. Считали, что для GHC нельзя добавить ни одну, ни три функции для поддержки существования библиотек этой формы, предполагает, что эта функция действительно полезна и не может быть использована во всех случаях. (НО мне все равно очень хотелось бы услышать об обходных решениях, которые помогут в этом конкретном случае: долгосрочная проблема не такая срочная, как проблема, с которой я сталкиваюсь прямо сейчас:-))
1 Технически я не совсем понимаю местоположение в памяти, поскольку сборщик мусора иногда перемещает объекты вокруг бит --- что я действительно имею в виду "идентичность объекта". Но мы можем думать об этом как о том же, что и наше интуитивное представление о местоположении в памяти.