Подходят ли одни структуры данных для функционального программирования больше, чем другие?

В Real World Haskell есть раздел под названием «Жизнь без массивов или хэш-таблиц», где авторы предлагают, чтобы списки и деревья были предпочтительнее в функциональном программировании, тогда как массив или хеш-таблица могли бы использоваться вместо них в императивной программе.

Это имеет смысл, поскольку намного проще повторно использовать часть (неизменяемого) списка или дерева при создании нового, чем использовать массив.

Итак, мои вопросы:

  • Действительно ли существуют существенно разные шаблоны использования структур данных при функциональном и императивном программировании?
  • Если да, то это проблема?
  • Что, если вам действительно нужна хеш-таблица для какого-то приложения? Вы просто проглатываете лишние расходы на модификации?

Ответов (9)

Решение

Книга « Чисто функциональные структуры данных» подробно рассматривает ваши вопросы и включает в себя отличное сочетание теории и реализаций, прежде всего в ML - приложение также содержит реализации Haskell, поэтому вы должны иметь возможность следить за ним, немного перелистывая страницы. Это довольно хорошее (хотя отчасти сложное) чтение, если вы действительно заинтересованы в исчерпывающих ответах на свои вопросы. Сказав это, я думаю, что ephemient дал превосходный короткий ответ.

edit: Стивен Хьювиг дал ссылку на тезис, с которого началась книга. Хотя я не читал его, единственное, чего не хватает (судя по оглавлению), - это реализации Haskell.

  • Действительно ли существуют существенно разные шаблоны использования структур данных при функциональном и императивном программировании?

Большой, гигантский, днем ​​и ночью - в основном потому, что в функциональном программировании не допускаются побочные эффекты.

  • Если да, то это проблема?

Проблема заключается в императивных парадигмах, которые не смогут сохранить эффективность, поскольку распараллеливание станет все более необходимым - единственный выход для этих языков - избавиться от побочных эффектов, но тогда они станут сломанными функциональными языками - но тогда зачем Я беспокоюсь о них, когда есть несколько неплохих рабочих функциональных языков. Кроме того, семантику функциональных языков легче контролировать, следовательно, функциональные программы можно доказать, а их аналоги на C++ - нет (пока, во всяком случае). Следовательно, многие формальные инструменты проверки основаны на функциональных языках - например, ACL2 основан на общем Lisp, а Cryptol основан на Haskell. Поскольку формальная проверка - это волна будущего, функциональные языки могут лучше интегрироваться с этими инструментами. Короче говоря, до свидания с C, C++ и т. Д. - хорошая поездка!

  • Что, если вам действительно нужна хеш-таблица для какого-то приложения? Вы просто проглатываете лишние расходы на модификации?

Волна будущего такова: вы пишете функциональное программирование с указанием хеш-таблицы - вы используете язык cryptol. Когда вы закончили и доказали, что ваша программа работает, вы нажимаете кнопку, и появляется эффективная версия, которая использует хеш-таблицу, если было решено, что это лучше всего использовать.

Что касается хэш-таблиц в функциональных языках: поскольку ACL2 был упомянут выше, я отмечу, что существует библиотека "хэш-минусов" для ACL2, которая обеспечивает логическую историю, которая в основном семантика списка ассоциаций, но имеет производительность хэш-таблицы. (например, вы можете найти значение в таблице с помощью hons-get). Если вам интересно, ознакомьтесь с темой «с отличием» в Руководствах пользователя ACL2.

Chris Okasaki's thesis, Purely Functional Data Structures, is available for free online. It covers many different strategies for immutable persistent data representation.

Что касается действительно необходимости в хэш-таблице, примите во внимание, что поиск O (lg n) всего в двадцать раз медленнее, чем поиск O (1), когда вы ищете миллион элементов.

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

Вы можете пересмотреть свое решение, если ищете дополнительные расходы на алгоритм. Почему хеш-таблица (которая является O (1) для нерекурсивного алгоритма) требует дополнительных затрат? Какое преимущество вы получаете, используя его, по сравнению с деревом или списком?

Да, основное отличие заключается в неизменности данных, которые могут включать код (см. Функции более высокого порядка). См. Страницу в Википедии о чисто функциональных возможностях, чтобы ознакомиться со списком распространенных типов данных и их использования. Будет ли это проблемой или нет, зависит от того, как вы на это смотрите. У программирования на функциональном языке есть много преимуществ, если он соответствует типу задачи, над которой вы работаете. Хеш-таблица - это тип ассоциативного массива, но не тот, который вы хотите использовать в функциональном языке из-за перехеширования, которое вам придется делать при вставке, и низкой производительности без массивов. Вместо этого попробуйте реализовать Data.Map в Haskell для ассоциативного массива.

  • да. Обычно кортежи, списки и частично вычисляемые функции являются очень распространенными структурами данных в языках функционального программирования. Изменяемые структуры данных, такие как массивы и (реальные) хеш-таблицы, используются гораздо реже, потому что они не подходят для Haskell. SML (который также является функциональным, но не ленивым) может использовать массивы более естественно, чем Haskell, но списки по-прежнему более распространены, поскольку они хорошо соответствуют рекурсивным алгоритмам.
  • Я не знаю, как на это ответить. Проблема для кого?
  • Существуют реализации ассоциативных массивов (эквивалент «хэш-таблицы»), которые могут продолжать использовать большую часть своей базовой структуры даже после различных обновлений. Я верю, что GHC Data.Mapделает; Кроме того, в Edison довольно много ленивых / функционально удобных структур данных.

Да, шаблоны использования разительно отличаются, но нет, это не проблема. Если вам нужна хеш-таблица, вы обычно имеете в виду, что вам нужна конечная карта со строковыми ключами и быстрым доступом. Тройные деревья поиска Bentley и Sedgewick полностью функциональны и, по крайней мере, в некоторых случаях, превосходят хеш-таблицы.

Как упоминалось выше, книга Криса Окасаки по чисто функциональным структурам данных очень хороша.

Выражаясь как человек, который много лет занимается объектно-ориентированным дизайном, а недавно создал крупный проект, требующий большой скорости (автоматическая система торговли опционами в реальном времени) на Haskell:

  • Действительно ли существуют существенно разные шаблоны использования структур данных при функциональном и императивном программировании?

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

  • Если да, то это проблема?

Для меня это не было чем-то особенным, за исключением того, что мне пришлось потратить некоторое время на изучение нового способа работы. В частности, производительность определенно не была проблемой: например, система, над которой я работаю, работает значительно быстрее, чем предыдущая реализация Java.

  • Что, если вам действительно нужна хеш-таблица для какого-то приложения? Вы просто проглатываете лишние расходы на модификации?

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