Что такое хороший способ для разработки / структурирования больших функциональных программ, особенно в Haskell?
Я прошел через кучу уроков («Пишу себе схему» - моя любимая, с «Реал Уорлд Хаскелл»), но большинство программ относительно небольшие и одноцелевые. Кроме того, я не считаю некоторые из них особенно элегантными (например, огромные таблицы поиска в WYAS).
Теперь я хочу писать более крупные программы с большим количеством движущихся частей - собирать данные из различных источников, очищать их, обрабатывать их различными способами, отображать в пользовательских интерфейсах, сохранять их, общаться по сетям и т. Д. Как можно Лучше ли структурировать такой код, чтобы он был удобочитаемым, поддерживаемым и адаптируемым к меняющимся требованиям?
Существует достаточно большая литература, посвященная этим вопросам для крупных объектно-ориентированных императивных программ. Такие идеи, как MVC, шаблоны проектирования и т. Д., Являются подходящими рецептами для реализации широких целей, таких как разделение задач и повторное использование в стиле ОО. Кроме того, новые императивные языки поддаются «рефакторингу» в стиле «дизайн по мере роста», который, по моему мнению новичка, выглядит менее подходящим для Haskell.
Есть ли эквивалентная литература для Haskell? Как зоопарк экзотических структур управления, доступных в функциональном программировании (монады, стрелки, аппликативные и т. Д.), Лучше всего использовать для этой цели? Какие лучшие практики вы можете порекомендовать?
Спасибо!
РЕДАКТИРОВАТЬ (это продолжение ответа Дона Стюарта):
@dons упомянул: «Монады захватывают ключевые архитектурные проекты в типах».
Я предполагаю, что мой вопрос: как следует думать о ключевых архитектурных проектах на чистом функциональном языке?
Рассмотрим пример нескольких потоков данных и нескольких этапов обработки. Я могу написать модульные парсеры для потоков данных для набора структур данных, и я могу реализовать каждый шаг обработки как чистую функцию. Шаги обработки, требуемые для одного куска данных, будут зависеть от его значения и других. Некоторые шаги должны сопровождаться побочными эффектами, такими как обновления графического интерфейса или запросы к базе данных.
Какой «правильный» способ связать данные и этапы синтаксического анализа хорошим способом? Можно написать большую функцию, которая делает правильные вещи для различных типов данных. Или можно использовать монаду для отслеживания того, что было обработано до сих пор, и чтобы каждый шаг обработки получал все необходимое из состояния монады. Или можно написать в значительной степени отдельные программы и отправлять сообщения (мне не очень нравится этот вариант).
На слайдах, которые он связал, есть пункт «Вещи, которые нам нужны»: «Идиомы для отображения дизайна на типы / функции / классы / монады». Какие идиомы? :)
Ответы:
Я немного говорю об этом в Проектировании крупных проектов в Haskell и в Проектировании и реализации XMonad. Инжиниринг в целом - это управление сложностью. Основные механизмы структурирования кода в Haskell для управления сложностью:
Система типов
Профилировщик
чистота
тестирование
Монады для структурирования
Классы типов и экзистенциальные типы
Параллелизм и параллелизм
par
в свою программу, чтобы победить конкурентов с легким, составным параллелизмом.Refactor
Используйте FFI с умом
Метапрограммирование
Упаковка и распространение
Предупреждения
-Wall
чтобы сохранить ваш код чистым от запахов. Вы также можете посмотреть на Агду, Изабель или Поймать для большей уверенности. Для проверки, похожей на ворсину , см. Отличный совет , который предложит улучшения.Со всеми этими инструментами вы можете справляться со сложностью, удаляя как можно больше взаимодействий между компонентами. В идеале, у вас есть очень большая база чистого кода, которую очень легко поддерживать, поскольку она композиционная. Это не всегда возможно, но к этому стоит стремиться.
В общем: разбейте логические блоки вашей системы на наименьшие из возможных ссылочных прозрачных компонентов, а затем внедрите их в модули. Глобальные или локальные среды для наборов компонентов (или внутри компонентов) могут быть сопоставлены с монадами. Используйте алгебраические типы данных для описания основных структур данных. Разделите эти определения широко.
источник
Дон дал вам большинство деталей выше, но вот мои два цента от выполнения по-настоящему жутких программ с отслеживанием состояния, таких как системные демоны в Haskell.
В конце концов, вы живете в монадном стеке трансформаторов. Внизу находится IO. Кроме того, каждый основной модуль (в абстрактном смысле, а не в смысле «модуль в файле») отображает свое необходимое состояние в слой в этом стеке. Таким образом, если у вас есть код подключения к базе данных, скрытый в модуле, вы пишете все, чтобы иметь тип подключения MonadReader m => ... -> m ... и тогда функции вашей базы данных всегда могут получить свое соединение без функций других модули должны быть осведомлены о его существовании. Вы можете получить один слой с вашим подключением к базе данных, другой - вашу конфигурацию, третий - различные семафоры и мвари для разрешения параллелизма и синхронизации, другой - дескрипторы вашего файла журнала и т. Д.
Сначала разберитесь с обработкой ошибок . Наибольшим недостатком в настоящее время для Haskell в больших системах является множество методов обработки ошибок, в том числе паршивых, таких как Maybe (что неверно, потому что вы не можете вернуть информацию о том, что пошло не так; всегда используйте Either вместо Maybe, если вы действительно просто имею ввиду пропущенные значения). Сначала выясните, как вы собираетесь это сделать, и настройте адаптеры из различных механизмов обработки ошибок, которые используются вашими библиотеками и другим кодом, в конечный. Это спасет вас от горя позже.
Приложение (извлечено из комментариев; спасибо Lii & liminalisht ) -
более подробное обсуждение различных способов нарезки большой программы на монады в стеке:
Бен Колера дает большое практическое введение в эту тему, а Брайан Херт обсуждает пути решения проблемы
lift
внедрения монадических действий в вашу собственную монаду. Джордж Уилсон показывает, как использоватьmtl
для написания кода, который работает с любой монадой, которая реализует требуемые классы типов, а не с вашим собственным видом монад. Карло Хамалайнен написал несколько коротких полезных заметок, в которых резюмируется выступление Джорджа.источник
lift
внедрения монадических действий в вашу собственную монаду. Джордж Уилсон показывает, как использоватьmtl
для написания кода, который работает с любой монадой, которая реализует требуемые классы типов, а не с вашим собственным видом монад. Карло Хамалайнен написал несколько коротких полезных заметок, в которых резюмируется выступление Джорджа.Проектирование больших программ на Haskell ничем не отличается от разработки на других языках. Программирование в целом заключается в том, чтобы разбить вашу проблему на управляемые части и как их объединить; язык реализации менее важен.
Тем не менее, в большом дизайне неплохо бы попробовать и использовать систему типов, чтобы убедиться, что вы можете соединять свои куски только так, чтобы это было правильно. Это может включать в себя новые или фантомные типы, чтобы вещи, которые выглядели одинаково, были разными.
Когда дело доходит до рефакторинга кода по мере продвижения, чистота - это большое благо, поэтому старайтесь сохранять как можно большую часть кода в чистоте. Чистый код легко реорганизовать, потому что он не имеет скрытого взаимодействия с другими частями вашей программы.
источник
С этой книгой я впервые узнал о структурном функциональном программировании . Это может быть не совсем то, что вы ищете, но для начинающих в функциональном программировании, это может быть одним из лучших первых шагов, чтобы научиться структурировать функциональные программы - независимо от масштаба. На всех уровнях абстракции дизайн всегда должен иметь четко организованные структуры.
Ремесло функционального программирования
http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/
источник
where beginner=do write $ tutorials `about` Monads
)Сейчас я пишу книгу под названием «Функциональный дизайн и архитектура». Он предоставляет вам полный набор методик создания больших приложений с использованием чисто функционального подхода. Он описывает множество функциональных шаблонов и идей при создании SCADA-подобного приложения «Андромеда» для управления космическими кораблями с нуля. Мой основной язык - Haskell. Книга охватывает:
Вы можете ознакомиться с кодом книги здесь и кодом проекта «Андромеды» .
Я рассчитываю закончить эту книгу в конце 2017 года. Пока это не произойдет, вы можете прочитать мою статью «Дизайн и архитектура в функциональном программировании» здесь .
ОБНОВИТЬ
Я поделился своей книгой онлайн (первые 5 глав). Смотрите пост на Reddit
источник
Блог Габриэля. Стоит упомянуть о масштабируемой архитектуре программ .
Меня часто поражает, что, очевидно, элегантная архитектура часто имеет тенденцию выпадать из библиотек, которые демонстрируют это приятное чувство однородности, восходящим способом. В Haskell это особенно очевидно - шаблоны, которые традиционно считаются «нисходящей архитектурой», обычно записываются в таких библиотеках, как mvc , Netwire и Cloud Haskell . То есть, я надеюсь, что этот ответ не будет интерпретирован как попытка заменить кого-либо из других в этой теме, просто что структурные решения могут и должны в идеале абстрагироваться экспертами в библиотеках. На мой взгляд, реальная трудность в построении больших систем заключается в оценке этих библиотек по их архитектурному «достоинству» в сравнении со всеми вашими прагматическими соображениями.
Как упоминает liminalisht в комментариях, шаблон дизайна категории - это еще одно сообщение Габриэля на эту тему, в том же духе.
источник
Я нашел статью Алехандро Серрано « Обучение архитектуре программного обеспечения с использованием Haskell » (pdf) , полезную для размышлений о крупномасштабной структуре в Haskell.
источник
Возможно, вам придется сделать шаг назад и подумать о том, как в первую очередь перевести описание проблемы в дизайн. Поскольку Haskell находится на таком высоком уровне, он может захватить описание проблемы в виде структур данных, действий в качестве процедур и чистого преобразования в качестве функций. Тогда у вас есть дизайн. Разработка начинается, когда вы компилируете этот код и обнаружите конкретные ошибки, касающиеся пропущенных полей, пропущенных экземпляров и пропущенных монадических преобразователей в вашем коде, потому что, например, вы выполняете доступ к базе данных из библиотеки, которой требуется определенная монада состояний в процедуре ввода-вывода. И вуаля, есть программа. Компилятор подпитывает ваши мысленные наброски и обеспечивает согласованность дизайна и разработки.
Таким образом, вы получаете выгоду от помощи Haskell с самого начала, и кодирование является естественным. Я не хотел бы делать что-то «функциональное» или «чистое» или достаточно общее, если то, что вы имеете в виду, является конкретной обычной проблемой. Я думаю, что чрезмерная инженерия - самая опасная вещь в ИТ. Все иначе, когда проблема заключается в создании библиотеки, которая абстрагирует набор связанных проблем.
источник