масштабный дизайн в haskell?

<p>что такое хороший способ для разработки/структурирования больших функциональных программ, особенно в haskell?</p><p>я прошел через кучу уроков ("пишу себе схему" - моя любимая, с "реал уорлд хаскелл"), но большинство программ относительно небольшие и одноцелевые. кроме того, я не считаю некоторые из них особенно элегантными (например, огромные таблицы поиска в wyas).</p><p>теперь я хочу писать более крупные программы с большим количеством движущихся частей - собирать данные из различных источников, очищать их, обрабатывать их различными способами, отображать в пользовательских интерфейсах, сохранять их, общаться по сетям и т.д. как можно лучше ли структурировать такой код, чтобы он был удобочитаемым, поддерживаемым и адаптируемым к меняющимся требованиям?</p><p>существует достаточно большая литература, посвященная этим вопросам для крупных объектно-ориентированных императивных программ. такие идеи, как mvc, шаблоны проектирования и т.д., являются подходящими рецептами для реализации широких целей, таких как разделение задач и возможность повторного использования в стиле oo. кроме того, новые императивные языки поддаются "рефакторингу" в стиле "дизайн по мере роста", который, по моему мнению новичка, выглядит менее подходящим для haskell.</p><p>есть ли эквивалентная литература для haskell? как зоопарк экзотических структур управления, доступных в функциональном программировании (монады, стрелки, аппликативные и т.д.), лучше всего используется для этой цели? какие лучшие практики вы можете порекомендовать?</p><p>спасибо!</p><p>редактировать (это продолжение ответа дона стюарта):</p><p>@dons упомянул: "монады захватывают ключевые архитектурные проекты в типах".</p><p>я предполагаю, что мой вопрос: как следует думать о ключевых архитектурных проектах на чистом функциональном языке?</p><p>рассмотрим пример нескольких потоков данных и нескольких этапов обработки. я могу написать модульные парсеры для потоков данных для набора структур данных, и я могу реализовать каждый шаг обработки как чистую функцию. шаги обработки, требуемые для одного куска данных, будут зависеть от его значения и других. некоторые шаги должны сопровождаться побочными эффектами, такими как обновления графического интерфейса или запросы к базе данных.</p><p>что такое "правильный" способ связать данные и этапы синтаксического анализа хорошим способом? можно написать большую функцию, которая делает правильные вещи для различных типов данных. или можно использовать монаду для отслеживания того, что было обработано до сих пор, и чтобы каждый шаг обработки получал все необходимое из состояния монады. или можно написать в значительной степени отдельные программы и отправлять сообщения (мне не очень нравится этот вариант).</p><p>на слайдах, которые он связал, есть пункт "вещи, которые нам нужны": "идиомы для отображения дизайна на типы/функции/классы/монады". какие идиомы? :)</p>

<p>я немного говорю об этом в <a href="https://www.scribd.com/doc/19502765/engineering-large-projects-in-haskell-a-decade-of-fp-at-galois">проектирование больших проектов в haskell</a> и в <a href="http://xmonad.wordpress.com/2009/09/09/the-design-and-implementation-of-xmonad/">проектирование и реализация xmonad.</a> проектирование в целом - это управление сложностью. основные механизмы структурирования кода в haskell для управления сложностью:</p><p><strong> система типов </strong></p><ul> <li>используйте систему типов для реализации абстракций, упрощая взаимодействия.</li> <li>применять ключевые инварианты с помощью типов <ul> <li>(например, определенные значения не могут выходить за рамки видимости)</li> <li>то, что определенный код не делает ввод-вывод, не касается диска</li> </ul> </li> <li>обеспечить безопасность: проверенные исключения (возможно/любые), избегать смешивания понятий (word, int, address)</li> <li>хорошие структуры данных (например, молнии) могут сделать ненужными некоторые классы тестирования, как они исключают, например, вне границ ошибки статически.</li> </ul><p><strong> профилировщик </strong></p><ul> <li>предоставьте объективные доказательства кучи и временных профилей вашей программы.</li> <li>в частности, профилирование кучи - лучший способ избежать ненужного использования памяти.</li> </ul><p><strong> чистота </strong></p><ul> <li>значительно уменьшите сложность, удалив состояние. чисто функциональный код масштабируется, потому что он композиционный. все, что вам нужно, это тип, чтобы определить, как использовать некоторый код - он не будет таинственным образом разрушаться, когда вы изменяете какую-то другую часть программы.</li> <li>используйте множество программ в стиле "модель/представление/контроллер": как можно скорее разберите внешние данные в чисто функциональные структуры данных, оперируйте этими структурами, а затем, как только вся работа будет выполнена, выполните рендеринг/очистку/сериализацию. сохраняет большую часть вашего кода в чистоте</li> </ul><p><strong> тестирование </strong></p><ul> <li>quickcheck + haskell code coverage, чтобы убедиться, что вы тестируете вещи, которые вы не можете проверить с помощью типов.</li> <li>ghc + rts отлично подходит для проверки того, что вы слишком много времени проводите в gc.</li> <li>quickcheck также может помочь вам определить чистые, ортогональные api для ваших модулей. если свойства вашего кода сложно определить, они, вероятно, слишком сложны. продолжайте рефакторинг до тех пор, пока у вас не будет чистого набора свойств, которые могут проверить ваш код, которые хорошо сочетаются. тогда код, вероятно, тоже хорошо спроектирован.</li> </ul><p><strong> монады для структурирования </strong></p><ul> <li>монады фиксируют ключевые архитектурные проекты по типам (этот код обращается к оборудованию, этот код является однопользовательским сеансом и т.д.)</li> <li>например. x-монада в xmonad точно отражает дизайн того, какое состояние видно для каких компонентов системы.</li> </ul><p><strong> типы классов и экзистенциальные типы </strong></p><ul> <li>используйте классы типов для обеспечения абстракции: скрыть реализации за полиморфными интерфейсами.</li> </ul><p><strong> параллелизм и параллелизм </strong></p><ul> <li>вставьте <code>par</code> в свою программу, чтобы победить конкурентов с легким, составным параллелизмом.</li> </ul><p><strong> refactor </strong></p><ul> <li>вы можете рефакторинг в haskell <strong> много </strong>. типы гарантируют, что ваши масштабные изменения будут безопасны, если вы используете типы с умом. это поможет вашему масштабу кодовой базы. убедитесь, что ваши рефакторинги вызовут ошибки типа до завершения.</li> </ul><p><strong> используйте ffi с умом </strong></p><ul> <li>ffi облегчает игру с иностранным кодом, но этот иностранный код может быть опасным.</li> <li>будьте очень осторожны в предположениях о форме возвращаемых данных.</li> </ul><p><strong> метапрограммирование </strong></p><ul> <li>немного шаблона хаскель или дженерики могут удалить шаблон.</li> </ul><p><strong> упаковка и распространение </strong></p><ul> <li>используйте кабал. не катите свою собственную систему сборки. <strong> (редактировать: на самом деле вы, вероятно, хотите использовать <a href="http://haskellstack.com">stack</a> сейчас для начала работы.). </strong></li> <li>используйте haddock для хороших документов api</li> <li>такие инструменты, как <a href="http://hackage.haskell.org/package/graphmod">graphmod</a> , могут показать структуру вашего модуля.</li> <li>если возможно, положитесь на версии библиотек и инструментов на платформе haskell. это стабильная база. <strong> (редактировать: опять же, в эти дни вы, вероятно, захотите использовать <a href="http://haskellstack.com">stack</a> для получения стабильной базы и запуска.) </strong></li> </ul><p><strong> предупреждения </strong></p><ul> <li>используйте <code>-wall</code>, чтобы сохранить ваш код чистым от запахов. вы также можете посмотреть на агду, изабель или поймать для большей уверенности. для проверки, похожей на ворсину, смотрите большое <a href="http://hackage.haskell.org/package/hlint">подсказка</a> , которое предложит улучшения.</li> </ul><p>со всеми этими инструментами вы можете справляться со сложностью, удаляя как можно больше взаимодействий между компонентами. в идеале, у вас есть очень большая база чистого кода, которую очень легко поддерживать, поскольку она композиционная. это не всегда возможно, но к этому стоит стремиться.</p><p>в общем: <strong> разложить </strong> логические единицы вашей системы на наименьшие из возможных ссылочных прозрачных компонентов, а затем внедрить их в модули. глобальные или локальные среды для наборов компонентов (или внутри компонентов) могут быть сопоставлены с монадами. используйте алгебраические типы данных для описания основных структур данных. разделите эти определения широко.</p>

<p>дон дал вам большинство деталей выше, но вот мои два цента от выполнения по-настоящему жутких программ с отслеживанием состояния, таких как системные демоны в haskell.</p><ol> <li> <p>в конце концов, вы живете в монадном стеке трансформаторов. внизу находится io. кроме того, каждый основной модуль (в абстрактном смысле, а не в смысле "модуль в файле") отображает свое необходимое состояние в слой в этом стеке. таким образом, если у вас есть код подключения к базе данных, скрытый в модуле, вы пишете все, чтобы иметь тип подключения monadreader m => ... -> m ... и тогда функции вашей базы данных всегда могут получить свое соединение без функций других модули должны быть осведомлены о его существовании. вы можете получить один слой с вашим подключением к базе данных, другой - вашу конфигурацию, третий - различные семафоры и мвари для разрешения параллелизма и синхронизации, другой - дескрипторы вашего файла журнала и т.д.</p> </li> <li> <p>сначала разберитесь с обработкой ошибок <em> </em>. наибольшим недостатком на данный момент для haskell в больших системах является множество методов обработки ошибок, в том числе паршивых, таких как maybe (что неверно, потому что вы не можете вернуть информацию о том, что пошло не так; всегда используйте either вместо maybe, если только вы действительно просто имею ввиду пропущенные значения). сначала выясните, как вы собираетесь это сделать, и настройте адаптеры из различных механизмов обработки ошибок, которые используются вашими библиотеками и другим кодом, в ваш последний. это спасет вас от горя позже.</p> </li> </ol><p><strong> приложение </strong> (извлечено из комментариев; спасибо <a href="https://stackoverflow.com/users/452775/lii">lii</a> & <a href="https://stackoverflow.com/users/1667161/liminalisht">liminalisht</a> ) -<br> подробнее о различных способах нарезки большой программы на монады в стеке:</p><p> <a href="https://www.youtube.com/watch?v=pzouxmwiemg">бен колера</a> дает большое практическое введение в эту тему, а <a href="https://www.youtube.com/watch?v=8t8fjkisjus">брайан хёрт</a> обсуждает решения проблемы <code>lift</code>ing монадических действий в вашу собственную монаду. <a href="https://www.youtube.com/watch?v=gzpup5iuaqw">джордж уилсон</a> показывает, как использовать <code>mtl</code> для написания кода, который работает с любой монадой, реализующей требуемые классы типов, а не с вашим собственным видом монад. <a href="http://carlo-hamalainen.net/blog/2015/7/20/classy-mtl">карло хамалайнен</a> написал несколько коротких полезных заметок, обобщающих выступление джорджа.</p>

<p>проектирование больших программ на haskell ничем не отличается от выполнения на других языках. программирование в целом заключается в том, чтобы разбить вашу проблему на управляемые части и как их объединить; язык реализации менее важен.</p><p>тем не менее, в большом дизайне хорошо бы попробовать и использовать систему типов, чтобы убедиться, что вы можете соединять свои части только так, чтобы это было правильно. это может включать в себя новые или фантомные типы, чтобы вещи, которые выглядели одинаково, были разными.</p><p>когда дело доходит до рефакторинга кода по мере продвижения, чистота - это великое благо, поэтому старайтесь сохранять как можно большую часть кода в чистоте. чистый код легко реорганизовать, потому что он не имеет скрытого взаимодействия с другими частями вашей программы.</p>

<p>я впервые научился <em> структурированному </em> функциональному программированию с <a href="http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/">эта книга</a> . это может быть не совсем то, что вы ищете, но для начинающих в функциональном программировании, это может быть одним из лучших первых шагов, чтобы научиться структурировать функциональные программы - независимо от масштаба. на всех уровнях абстракции дизайн должен всегда иметь четко организованные структуры.</p><p>ремесло функционального программирования</p><p><img src="https://i.stack.imgur.com/dw3qy.gif" alt="the craft of functional programming"></p><p> <a href="http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/">http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/</a> </p>

<p>в настоящее время я пишу книгу под названием "функциональный дизайн и архитектура". он предоставляет вам полный набор методик создания больших приложений с использованием чисто функционального подхода. он описывает множество функциональных шаблонов и идей при создании scada-подобного приложения "андромеда" для управления космическими кораблями с нуля. мой основной язык - haskell. книга охватывает:</p><ul> <li>подходы к моделированию архитектуры с использованием диаграмм;</li> <li>анализ требований;</li> <li>моделирование встраиваемых доменов dsl;</li> <li>внешний дизайн и реализация dsl;</li> <li>монады как подсистемы с эффектами;</li> <li>бесплатные монады как функциональные интерфейсы;</li> <li>стрелки edsl;</li> <li>инверсия управления с использованием free monadic edsl;</li> <li>программная транзакционная память;</li> <li>линзы;</li> <li>state, reader, writer, rws, st монады;</li> <li>нечистое состояние: ioref, mvar, stm;</li> <li>многопоточность и параллельное доменное моделирование;</li> <li>gui;</li> <li>применимость основных методов и подходов, таких как uml, solid, grasp;</li> <li>взаимодействие с нечистыми подсистемами.</li> </ul><p>вы можете ознакомиться с кодом книги <a href="https://github.com/graninas/functional-design-and-architecture">здесь</a> и кодом проекта <a href="https://github.com/graninas/andromeda">'andromeda'</a> .</p><p>я рассчитываю закончить эту книгу в конце 2017 года. пока этого не произойдет, вы можете прочитать мою статью "дизайн и архитектура в функциональном программировании" (рус) <a href="https://docs.google.com/document/d/1gypzxp13ampzd7o5xamgy5zrrchqkrebjarm6hdmwqk/edit?usp=sharing">здесь</a> .</p><p><strong>обновить</strong></p><p>я поделился своей книгой онлайн (первые 5 глав). смотрите <a href="https://www.reddit.com/r/haskell/comments/6ck72h/functional\_design\_and\_architecture/">сообщение на reddit</a> </p>

<p>пост габриэля в блоге <a href="http://www.haskellforall.com/2014/04/scalable-program-architectures.html">масштабируемая архитектура программ</a> может стоить упомянуть.</p><blockquote> <p>шаблоны проектирования haskell отличаются от основных шаблонов проектирования одним важным аспектом:</p> <ul> <li> <p><strong> обычная архитектура </strong>: объединить несколько компонентов типа a для создания "сети" или "топологии" типа b</p> </li> <li> <p><strong> архитектура haskell </strong>: объединить несколько компонентов типа a для создания нового компонента того же типа a, неотличимого по характеру от его замещающих частей</p> </li> </ul> </blockquote><p>меня часто поражает, что, по-видимому, элегантная архитектура часто выпадает из библиотек, которые демонстрируют это приятное чувство однородности, восходящим способом. в haskell это особенно очевидно - шаблоны, которые традиционно считаются "нисходящей архитектурой", как правило, собираются в таких библиотеках, как <a href="http://www.haskellforall.com/2014/04/model-view-controller-haskell-style.html">mvc</a> , <a href="http://www.haskell.org/haskellwiki/netwire">netwire</a> и <a href="http://haskell-distributed.github.io/">cloud haskell</a> . то есть, я надеюсь, что этот ответ не будет интерпретирован как попытка заменить кого-либо из других в этой теме, просто что структурные решения могут и должны в идеале абстрагироваться экспертами в библиотеках. на мой взгляд, реальная трудность в построении больших систем заключается в оценке этих библиотек по их архитектурному "достоинству" в сравнении со всеми вашими прагматическими соображениями.</p><p>как <a href="https://stackoverflow.com/users/1667161/liminalisht">liminalisht</a> упоминается в комментариях, <a href="http://www.haskellforall.com/2012/08/the-category-design-pattern.html">шаблон дизайна категории</a> это еще одно сообщение габриэля на эту тему, в том же духе.</p>

<p>я нашел статью <a href="http://wiki.science.ru.nl/tfpie/images/5/56/tfpie2014\_submission\_5.pdf">"<em> обучающая программная архитектура с использованием haskell </em>" (pdf)</a> алехандро серрано полезной для размышлений о крупномасштабных структура в haskell.</p>

<p>возможно, вам придется сделать шаг назад и подумать, как в первую очередь перевести описание проблемы в дизайн. поскольку haskell находится на таком высоком уровне, он может захватить описание проблемы в виде структур данных, действий в качестве процедур и чистого преобразования в виде функций. тогда у вас есть дизайн. разработка начинается, когда вы компилируете этот код и обнаружите конкретные ошибки, касающиеся пропущенных полей, пропущенных экземпляров и пропущенных монадных преобразователей в вашем коде, потому что, например, вы выполняете доступ к базе данных из библиотеки, которой требуется определенная монада состояний в io процедура. и вуаля, есть программа. компилятор подпитывает ваши мысленные наброски и обеспечивает согласованность дизайна и разработки.</p><p>таким образом, вы получаете выгоду от помощи haskell с самого начала, и кодирование является естественным. я не хотел бы делать что-то "функциональное" или "чистое" или достаточно общее, если то, что вы имеете в виду, является конкретной обычной проблемой. я думаю, что чрезмерная инженерия - самая опасная вещь в ит. все иначе, когда проблема заключается в создании библиотеки, которая абстрагирует набор связанных проблем.</p>