it-swarm.com.ru

Масштабный дизайн в Haskell?

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

Я прошел через кучу уроков ("Пишу себе схему" - моя любимая, с "Реал Уорлд Хаскелл"), но большинство программ относительно небольшие и одноцелевые. Кроме того, я не считаю некоторые из них особенно элегантными (например, огромные таблицы поиска в WYAS).

Теперь я хочу писать более крупные программы с большим количеством движущихся частей - собирать данные из различных источников, очищать их, обрабатывать их различными способами, отображать в пользовательских интерфейсах, сохранять их, общаться по сетям и т.д. Как можно Лучше ли структурировать такой код, чтобы он был удобочитаемым, поддерживаемым и адаптируемым к меняющимся требованиям?

Существует достаточно большая литература, посвященная этим вопросам для крупных объектно-ориентированных императивных программ. Такие идеи, как MVC, шаблоны проектирования и т.д., Являются подходящими рецептами для реализации широких целей, таких как разделение задач и возможность повторного использования в стиле OO. Кроме того, новые императивные языки поддаются "рефакторингу" в стиле "дизайн по мере роста", который, по моему мнению новичка, выглядит менее подходящим для Haskell.

Есть ли эквивалентная литература для Haskell? Как Зоопарк экзотических структур управления, доступных в функциональном программировании (монады, стрелки, аппликативные и т.д.), Лучше всего используется для этой цели? Какие лучшие практики вы можете порекомендовать?

Спасибо!

РЕДАКТИРОВАТЬ (это продолжение ответа Дона Стюарта):

@dons упомянул: "Монады захватывают ключевые архитектурные проекты в типах".

Я предполагаю, что мой вопрос: как следует думать о ключевых архитектурных проектах на чистом функциональном языке?

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

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

На слайдах, которые он связал, есть пункт "Вещи, которые нам нужны": "Идиомы для отображения дизайна на типы/функции/классы/монады". Какие идиомы? :)

566
Dan

Я немного говорю об этом в Проектирование больших проектов в Haskell и в Проектирование и реализация XMonad. Проектирование в целом - это управление сложностью. Основные механизмы структурирования кода в Haskell для управления сложностью:

Система типов

  • Используйте систему типов для реализации абстракций, упрощая взаимодействия.
  • Применять ключевые инварианты с помощью типов
    • (например, определенные значения не могут выходить за рамки видимости)
    • То, что определенный код не делает ввод-вывод, не касается диска
  • Обеспечить безопасность: проверенные исключения (возможно/любые), избегать смешивания понятий (Word, Int, Address)
  • Хорошие структуры данных (например, молнии) могут сделать ненужными некоторые классы тестирования, как они исключают, например, Вне границ ошибки статически.

Профилировщик

  • Предоставьте объективные доказательства кучи и временных профилей вашей программы.
  • В частности, профилирование кучи - лучший способ избежать ненужного использования памяти.

Чистота

  • Значительно уменьшите сложность, удалив состояние. Чисто функциональный код масштабируется, потому что он композиционный. Все, что вам нужно, это тип, чтобы определить, как использовать некоторый код - он не будет таинственным образом разрушаться, когда вы изменяете какую-то другую часть программы.
  • Используйте множество программ в стиле "модель/представление/контроллер": как можно скорее разберите внешние данные в чисто функциональные структуры данных, оперируйте этими структурами, а затем, как только вся работа будет выполнена, выполните рендеринг/очистку/сериализацию. Сохраняет большую часть вашего кода в чистоте

Тестирование

  • QuickCheck + Haskell Code Coverage, чтобы убедиться, что вы тестируете вещи, которые вы не можете проверить с помощью типов.
  • GHC + RTS отлично подходит для проверки того, что вы слишком много времени проводите в GC.
  • QuickCheck также может помочь вам определить чистые, ортогональные API для ваших модулей. Если свойства вашего кода сложно определить, они, вероятно, слишком сложны. Продолжайте рефакторинг до тех пор, пока у вас не будет чистого набора свойств, которые могут проверить ваш код, которые хорошо сочетаются. Тогда код, вероятно, тоже хорошо спроектирован.

Монады для структурирования

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

Типы классов и экзистенциальные типы

  • Используйте классы типов для обеспечения абстракции: скрыть реализации за полиморфными интерфейсами.

Параллелизм и параллелизм

  • Вставьте par в свою программу, чтобы победить конкурентов с легким, составным параллелизмом.

Refactor

  • Вы можете рефакторинг в Haskell много . Типы гарантируют, что ваши масштабные изменения будут безопасны, если вы используете типы с умом. Это поможет вашему масштабу кодовой базы. Убедитесь, что ваши рефакторинги вызовут ошибки типа до завершения.

Используйте FFI с умом

  • FFI облегчает игру с иностранным кодом, но этот иностранный код может быть опасным.
  • Будьте очень осторожны в предположениях о форме возвращаемых данных.

Метапрограммирование

  • Немного шаблона Хаскель или дженерики могут удалить шаблон.

Упаковка и распространение

  • Используйте Кабал. Не катите свою собственную систему сборки. (РЕДАКТИРОВАТЬ: на самом деле вы, вероятно, хотите использовать Stack сейчас для начала работы.).
  • Используйте Haddock для хороших документов API
  • Такие инструменты, как graphmod , могут показать структуру вашего модуля.
  • Если возможно, положитесь на версии библиотек и инструментов на платформе Haskell. Это стабильная база. (РЕДАКТИРОВАТЬ: Опять же, в эти дни вы, вероятно, захотите использовать Stack для получения стабильной базы и запуска.)

Предупреждения

  • Используйте -Wall, чтобы сохранить ваш код чистым от запахов. Вы также можете посмотреть на Агду, Изабель или Поймать для большей уверенности. Для проверки, похожей на ворсину, смотрите большое подсказка , которое предложит улучшения.

Со всеми этими инструментами вы можете справляться со сложностью, удаляя как можно больше взаимодействий между компонентами. В идеале, у вас есть очень большая база чистого кода, которую очень легко поддерживать, поскольку она композиционная. Это не всегда возможно, но к этому стоит стремиться.

В общем: разложить логические единицы вашей системы на наименьшие из возможных ссылочных прозрачных компонентов, а затем внедрить их в модули. Глобальные или локальные среды для наборов компонентов (или внутри компонентов) могут быть сопоставлены с монадами. Используйте алгебраические типы данных для описания основных структур данных. Разделите эти определения широко.

519
Don Stewart

Дон дал вам большинство деталей выше, но вот мои два цента от выполнения по-настоящему жутких программ с отслеживанием состояния, таких как системные демоны в Haskell.

  1. В конце концов, вы живете в монадном стеке трансформаторов. Внизу находится IO. Кроме того, каждый основной модуль (в абстрактном смысле, а не в смысле "модуль в файле") отображает свое необходимое состояние в слой в этом стеке. Таким образом, если у вас есть код подключения к базе данных, скрытый в модуле, вы пишете все, чтобы иметь тип подключения MonadReader m => ... -> m ... и тогда функции вашей базы данных всегда могут получить свое соединение без функций других модули должны быть осведомлены о его существовании. Вы можете получить один слой с вашим подключением к базе данных, другой - вашу конфигурацию, третий - различные семафоры и мвари для разрешения параллелизма и синхронизации, другой - дескрипторы вашего файла журнала и т.д.

  2. Сначала разберитесь с обработкой ошибок . Наибольшим недостатком на данный момент для Haskell в больших системах является множество методов обработки ошибок, в том числе паршивых, таких как Maybe (что неверно, потому что вы не можете вернуть информацию о том, что пошло не так; всегда используйте Either вместо Maybe, если только вы действительно просто имею ввиду пропущенные значения). Сначала выясните, как вы собираетесь это сделать, и настройте адаптеры из различных механизмов обработки ошибок, которые используются вашими библиотеками и другим кодом, в ваш последний. Это спасет вас от горя позже.

Приложение (извлечено из комментариев; спасибо Lii & liminalisht ) -
подробнее о различных способах нарезки большой программы на монады в стеке:

Бен Колера дает большое практическое введение в эту тему, а Брайан Хёрт обсуждает решения проблемы lifting монадических действий в вашу собственную монаду. Джордж Уилсон показывает, как использовать mtl для написания кода, который работает с любой монадой, реализующей требуемые классы типов, а не с вашим собственным видом монад. Карло Хамалайнен написал несколько коротких полезных заметок, обобщающих выступление Джорджа.

118
user349653

Проектирование больших программ на Haskell ничем не отличается от выполнения на других языках. Программирование в целом заключается в том, чтобы разбить вашу проблему на управляемые части и как их объединить; язык реализации менее важен.

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

Когда дело доходит до рефакторинга кода по мере продвижения, чистота - это великое благо, поэтому старайтесь сохранять как можно большую часть кода в чистоте. Чистый код легко реорганизовать, потому что он не имеет скрытого взаимодействия с другими частями вашей программы.

43
augustss

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

Ремесло функционального программирования

The Craft of Functional Programming

http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/

16
comonad

В настоящее время я пишу книгу под названием "Функциональный дизайн и архитектура". Он предоставляет вам полный набор методик создания больших приложений с использованием чисто функционального подхода. Он описывает множество функциональных шаблонов и идей при создании SCADA-подобного приложения "Андромеда" для управления космическими кораблями с нуля. Мой основной язык - Haskell. Книга охватывает:

  • Подходы к моделированию архитектуры с использованием диаграмм;
  • Анализ требований;
  • Моделирование встраиваемых доменов DSL;
  • Внешний дизайн и реализация DSL;
  • Монады как подсистемы с эффектами;
  • Бесплатные монады как функциональные интерфейсы;
  • Стрелки eDSL;
  • Инверсия управления с использованием Free monadic eDSL;
  • Программная транзакционная память;
  • Линзы;
  • State, Reader, Writer, RWS, ST монады;
  • Нечистое состояние: IORef, MVar, STM;
  • Многопоточность и параллельное доменное моделирование;
  • GUI;
  • Применимость основных методов и подходов, таких как UML, SOLID, GRASP;
  • Взаимодействие с нечистыми подсистемами.

Вы можете ознакомиться с кодом книги здесь и кодом проекта 'Andromeda' .

Я рассчитываю закончить эту книгу в конце 2017 года. Пока этого не произойдет, вы можете прочитать мою статью "Дизайн и архитектура в функциональном программировании" (Рус) здесь .

ОБНОВИТЬ

Я поделился своей книгой онлайн (первые 5 глав). Смотрите сообщение на Reddit

11
graninas

Пост Габриэля в блоге Масштабируемая архитектура программ может стоить упомянуть.

Шаблоны проектирования Haskell отличаются от основных шаблонов проектирования одним важным аспектом:

  • Обычная архитектура : объединить несколько компонентов типа A для создания "сети" или "топологии" типа B

  • Архитектура Haskell : объединить несколько компонентов типа A для создания нового компонента того же типа A, неотличимого по характеру от его замещающих частей

Меня часто поражает, что, по-видимому, элегантная архитектура часто выпадает из библиотек, которые демонстрируют это приятное чувство однородности, восходящим способом. В Haskell это особенно очевидно - шаблоны, которые традиционно считаются "нисходящей архитектурой", как правило, собираются в таких библиотеках, как mvc , Netwire и Cloud Haskell . То есть, я надеюсь, что этот ответ не будет интерпретирован как попытка заменить кого-либо из других в этой теме, просто что структурные решения могут и должны в идеале абстрагироваться экспертами в библиотеках. На мой взгляд, реальная трудность в построении больших систем заключается в оценке этих библиотек по их архитектурному "достоинству" в сравнении со всеми вашими прагматическими соображениями.

Как liminalisht упоминается в комментариях, Шаблон дизайна категории это еще одно сообщение Габриэля на эту тему, в том же духе.

7
Rehno Lindeque

Я нашел статью " Обучающая программная архитектура с использованием Haskell " (pdf) Алехандро Серрано полезной для размышлений о крупномасштабных структура в Haskell.

5
haroldcarr

Возможно, вам придется сделать шаг назад и подумать, как в первую очередь перевести описание проблемы в дизайн. Поскольку Haskell находится на таком высоком уровне, он может захватить описание проблемы в виде структур данных, действий в качестве процедур и чистого преобразования в виде функций. Тогда у вас есть дизайн. Разработка начинается, когда вы компилируете этот код и обнаружите конкретные ошибки, касающиеся пропущенных полей, пропущенных экземпляров и пропущенных монадных преобразователей в вашем коде, потому что, например, вы выполняете доступ к базе данных из библиотеки, которой требуется определенная монада состояний в IO процедура. И вуаля, есть программа. Компилятор подпитывает ваши мысленные наброски и обеспечивает согласованность дизайна и разработки.

Таким образом, вы получаете выгоду от помощи Haskell с самого начала, и кодирование является естественным. Я не хотел бы делать что-то "функциональное" или "чистое" или достаточно общее, если то, что вы имеете в виду, является конкретной обычной проблемой. Я думаю, что чрезмерная инженерия - самая опасная вещь в ИТ. Все иначе, когда проблема заключается в создании библиотеки, которая абстрагирует набор связанных проблем.

3
agocorona