2010-06-20 2 views
534

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

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

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

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

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

Спасибо!

EDIT (это прослеживание ответить Дон Стюарт):

@dons отметил: «Монады захватить ключевые архитектурные проекты типов»

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

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

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

Слайды, с которыми он связан, имеют Вещь Нам нужна пуля: «Идиомы для картирования дизайна на типы/функции/классы/монады». Что такое идиомы? :)

+9

Я думаю, что основная идея при написании больших программ на функциональном языке - это небольшие, специализированные и безстоящие модули, обменивающиеся сообщениями через передачу сообщений *. Конечно, вы должны притвориться немного, потому что истинная программа нуждается в состоянии. Я думаю, что здесь F # сияет над Haskell. – ChaosPandion

+18

@Chaos, но только Haskell по умолчанию делает безгражданство. У вас нет выбора, и вам нужно много работать, чтобы ввести состояние (нарушить композицию) в Haskell :-) –

+0

@ Don - Да, я знаю, но я один из лучших оба ребята типа миров. – ChaosPandion

ответ

494

Я немного об этом расскажу в Engineering Large Projects in Haskell и в Design and Implementation of XMonad. Инженерное дело в целом об управлении сложностью. Основные механизмы структурирования кода в Haskell для управления сложностью являются:

Тип системы

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

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

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

Чистота

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

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

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

монады для Структурирование

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

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

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

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

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

Refactor

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

Используйте FFI благоразумно

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

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

  • Немного Template Haskell или воспроизведенных может удалить шаблонного.

Упаковка и распределение

  • Использование Cabal. Не сворачивайте свою собственную систему сборки. (EDIT: На самом деле вы, вероятно, захотите использовать Stack сейчас для начала работы.).
  • Использование пикши для хороших документов API
  • Инструменты, такие как graphmod, могут отображать структуры вашего модуля.
  • Положитесь на версии библиотек и инструментов платформы Haskell, если это вообще возможно. Это стабильная база. (EDIT: Опять же, в эти дни вы, вероятно, хотите использовать Stack для получения стабильной базы и работает.)

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

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

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

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

+7

Спасибо, Дон, ваш ответ превосходный - все это ценные рекомендации, и я буду регулярно обращайтесь к ним. Я предполагаю, что мой вопрос возникает шаг, прежде чем понадобится все это. То, что я действительно хотел бы узнать, это «Идиомы для картографирования дизайна на типы/функции/классы/монады» ... Я мог бы попытаться изобрести свои собственные, но я надеялся, что где-то может быть множество лучших практик, или, если нет, рекомендации по хорошо структурированному коду для чтения крупной системы (в отличие от, скажем, целенаправленной библиотеки). Я отредактировал мой пост, чтобы задать тот же вопрос более напрямую. – Dan

+5

Я добавил текст о разложении дизайна на модули. Ваша цель состоит в том, чтобы идентифицировать логически связанные функции в модули, которые имеют ссылочно прозрачные интерфейсы с другими частями системы, и как можно скорее использовать чисто функциональные типы данных, чтобы максимально безопасно моделировать внешний мир. Документ дизайна xmonad охватывает много этого: http://xmonad.wordpress.com/2009/09/09/the-design-and-implementation-of-xmonad/ –

+0

Еще раз спасибо! Конструктивный документ xmonad - это то, что я искал. Время читать код ... – Dan

43

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

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

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

+13

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

+2

@Dan Вы можете полностью освободиться с меньшими изменениями (например, просто добавлением поля) при использовании записей. Некоторые могут захотеть сделать запись привычкой (я один из них ^^). – MasterMastic

+2

@ Dan Я имею в виду, если вы измените тип данных функции на любом языке, вам не нужно делать то же самое? Посмотрите, как вам поможет этот язык, такой как Java или C++. Если вы скажете, что можете использовать какой-то общий интерфейс, которым подчиняются оба типа, тогда вы должны были делать это с помощью Typeclasses в Haskell. – semicolon

116

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

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

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

Добавление (извлеченное из комментариев, благодаря Lii & liminalisht) —
более обсуждения различных способов ломтика большой программы в монады в стеке:

Ben Kolera дает большое практическое введение к этой теме, и Brian Hurt обсуждает решения проблемы с монадическими действиями в вашу обычную монаду. George Wilson показывает, как использовать mtl для написания кода, который работает с любой монадой, которая реализует требуемые классы, а не ваш пользовательский вид монады. Carlo Hamalainen написал несколько коротких, полезных заметок, в которых резюмировал разговор Джорджа.

+5

Два хороших момента! Этот ответ заслуживает того, чтобы быть достаточно конкретным, а другие - нет. Было бы интересно прочитать больше дискуссий о различных способах разрезать большую программу на монады в стеке. Пожалуйста, напишите ссылки на такие статьи, если у вас есть! – Lii

+4

@Lii [Ben Kolera] (https://www.youtube.com/watch?v=pzouxmWiemg) дает большое практическое введение в эту тему, а также [Брайан Херт] (https://www.youtube.com/watch ? v = 8t8fjkISjus) обсуждает решения проблемы «поднять монадические действия в вашу обычную монаду». [George Wilson] (https://www.youtube.com/watch?v=GZPup5Iuaqw) показывает, как использовать 'mtl' для написания кода, который работает с любой монадой, которая реализует требуемые типы имен, а не ваш пользовательский вид монады. [Carlo Hamalainen] (http://carlo-hamalainen.net/blog/2015/7/20/classy-mtl) написал несколько коротких полезных заметок, в которых излагался разговор Джорджа. – liminalisht

+0

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

15

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

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

The Craft of Functional Programming

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

+11

As как Craft of FP - я изучил Haskell от него - это * вводный текст * для начинающих программистов *, а не для разработки больших систем в Haskell. –

+3

Ну, это лучшая книга, которую я знаю о разработке API и скрытии деталей реализации. С этой книгой я стал лучшим программистом на C++ - только потому, что узнал, как лучше организовать мой код. Ну, ваш опыт (и ответ), безусловно, лучше, чем эта книга, но Дэн, вероятно, все еще может быть _beginner_ в Haskell. ('где beginner = do write $ tutorials \' about \ 'Monads') – comonad

2

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

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

7

Возможно, стоит отметить сообщение блога Gabriel Scalable program architectures.

модель

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

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

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

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

В liminalisht упоминается в комментариях, The category design pattern - это еще одно сообщение Габриэля по теме, в том же духе.

+3

Я бы упомянул еще одно сообщение Габриэля Гонсалеса по [шаблону дизайна категории] (http://www.haskellforall.com/2012/08/the- категория дизайн-pattern.html). Его основной аргумент состоит в том, что то, что мы, функциональные программисты, считаем «хорошей архитектурой», действительно «композиционной архитектурой» - это разработка программ с использованием элементов, которые гарантированно скомпилируются. Поскольку законы категории гарантируют сохранение идентичности и ассоциативности по составу, композиционная архитектура достигается за счет использования абстракций, для которых у нас есть категория - например, чистые функции, монадические действия, трубы и т. д. – liminalisht

11

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

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

Код проекта here и код 'Andromeda'.

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

UPDATE

Я разделил свою книгу в Интернете (первые 5 глав). См. post on Reddit

+0

Александр, не могли бы вы любезно обновить эту заметку, когда закончите книгу, чтобы мы могли ее проследить. Приветствия. – Max

+4

Конечно! Пока я закончил половину текста, но это 1/3 общей работы. Итак, держите свой интерес, это очень меня вдохновляет! – GAS

+2

Привет! Я поделился своей книгой в Интернете (только первые 5 глав). См. Сообщение на Reddit: https://www.reddit.com/r/haskell/comments/6ck72h/functional_design_and_architecture/ – GAS