Обходной путь для выполнения операций над двусвязными или циклическими структурами данных в языках с неизменяемыми данными

11

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

Вопрос: Каким был бы идиоматический или рекомендуемый способ реализации двусвязного списка (или другой двусвязной или круговой структуры данных) и операций с ним на языке, который в основном поддерживает и поддерживает неизменные структуры данных (Haskell, Clojure и т. Д.) ? В частности, как использовать обновления на месте, которые формально запрещены языком?

Я легко могу представить, что если какая-то локальная операция выполняется над двусвязным списком (например, если элемент вставлен), может не потребоваться копировать весь список сразу из-за лени языка. Однако, поскольку список является двусвязным, если его изменить в одном месте, ни один из старых узлов не может быть использован в новой версии списка, и их рано или поздно придется каким-то образом пометить, скопировать, собрать мусор. , Очевидно, что это избыточные операции, если будет использоваться только обновленная копия списка, но они добавят «накладные расходы», пропорциональные размеру списка.

Означает ли это, что для таких задач неизменяемые данные просто неуместны, а функциональные декларативные языки без «нативной» поддержки изменяемых данных не так хороши, как императивные? Или есть какой-то хитрый обходной путь?

PS Я нашел несколько статей и презентаций на эту тему в Интернете, но с трудом следил за ними, хотя я думаю, что ответ на этот вопрос не должен занимать более одного абзаца и, возможно, диаграммы ... Я имею в виду, если есть нет «функционального» решения этой проблемы, ответ, вероятно, «использовать C». Если есть, то насколько это может быть сложно?


Смежные вопросы


Соответствующие цитаты

Чисто функциональные языки программирования позволяют выразить многие алгоритмы очень кратко, но есть несколько алгоритмов, в которых обновляемое состояние на месте, кажется, играет решающую роль. Для этих алгоритмов чисто функциональные языки, в которых отсутствует обновляемое состояние, по своей природе оказываются неэффективными ( [Ponder, McGeer and Ng, 1988] ).

- Джон Лончбери и Саймон Пейтон Джонс, « Ленивые потоки функционального состояния» (1994), а также Джон Лончбери и Саймон Пейтон Джонс, « Штат в Хаскелле» (1995). Эти документы вводят STконструктор монадического типа в Haskell.

Алексей
источник
4
Рекомендовано: Окасаки
Роберт Харви
2
Спасибо за ссылку. Я нашел его тезис .
Алексей
Эта статья выглядит многообещающе: ленивый поиск в глубину и алгоритмы линейного графа в Haskell (1994), авторы David King и John Launchbury.
Алексей
Похоже, что аналогичная проблема с массивами решается с помощью пакета diffarray , который реализует DiffArrayтип. Глядя на источник из diffarray пакета, я вижу 91 вхождений unsafePerformIO. Похоже, что ответ на мой вопрос: «Да, нет, чисто функциональные языки с неизменяемыми данными не подходят для реализации алгоритмов, которые обычно полагаются на обновления на месте».
Алексей
Мое текущее решение (в Haskell) заключается в использовании словаря ( Map, IntMapили HashMap) в качестве хранилища и сделать узлы содержат идентификаторы связанных узлов. «Все проблемы в информатике могут быть решены с помощью другого уровня косвенности».
Алексей

Ответы:

6

Могут быть и другие эффективные неизменяемые структуры данных, которые соответствуют вашей конкретной задаче, но не такие общие, как двусвязный список (который, к сожалению, подвержен параллельным ошибкам модификации из-за своей изменчивости). Если вы укажете свою проблему более узко, такую ​​структуру, вероятно, можно найти.

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

Еще одна полезная конструкция - молния . (Самое смешное в том, что сигнатура типа для молнии линзы является производной математической производной от сигнатуры типа структуры.)

Вот несколько ссылок.

9000
источник
1
в зависимости от того, что необходимо, молния также может быть полезна
JK.
Чтобы более точно определить мою проблему, предположим, что я хочу запрограммировать систему переписывания графа, например, оценщик лямбда-исчисления, основанный на переписывании графа.
Алексей
1
@Alexey: Вы знакомы с работой Чистых людей по переписыванию графов? wiki.clean.cs.ru.nl/…
Джорджио
1
@Alexey: Не то, чтобы я знал: Clean - двоюродный брат Haskell, который был разработан самостоятельно. У этого также есть другой механизм для того, чтобы иметь дело с побочными эффектами (AFAIK это называют уникальными типами). С другой стороны, разработчики много работали над переписыванием графиков. Таким образом, они могли бы быть среди лучших людей, которые знают как о переписывании графов, так и о функциональном программировании.
Джорджио
1
Я согласен, что молния, кажется, решает проблему с помощью двусвязного списка или дерева, если я хочу перемещаться по нему и изменять его в том месте, где я сейчас нахожусь, но не ясно, что делать, если я хочу сосредоточиться на нескольких местах одновременно и, например, поменять два элемента в двух местах далеко друг от друга. Еще менее понятно, можно ли использовать «круглые» структуры.
Алексей
2

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

Я бы предложил исследовать использование программной транзакционной памяти в качестве пути продвижения вперед. Помимо обеспечения эффективного способа реализации изменяемых структур, он также предоставляет очень полезные гарантии безопасности потоков. См. Описание модуля по адресу https://hackage.haskell.org/package/stm и обзор вики по адресу https://wiki.haskell.org/Software_transactional_memory .

Жюль
источник
Спасибо, я постараюсь узнать о STM. Похоже , есть еще методы в Haskell , чтобы иметь переменчивость и состояние (я натыкается на MVar, State, ST), так что я буду нуждаться , чтобы выяснить их различия и предполагаемого использования.
Алексей
@Alexey: Хороший вопрос относительно STIMO, это должно быть упомянуто в ответе, потому что оно позволяет выполнить вычисление с учетом состояния, затем отбросить состояние и извлечь результат как чистое значение.
Джорджио
@ Джорджио, возможно ли использовать Haskell STс STM для одновременного и одноразового состояния?
Алексей
Еще одно предложение по терминологии: составное основное действие ввода-вывода не « возвращается основной функцией», а присваивается mainпеременной. :) ( mainдаже не держит функции.)
Алексей
Я понимаю вашу точку зрения, но все же «переменная» в сознании большинства людей имеет значение как простое значение, а не как процесс, который производит значение, и главное явно лучше воспринимать как второе, а не первое. Предложенное вами изменение, хотя и явно технически правильное, может запутать тех, кто не знаком с предметом.
Жюль