Подтвердить что ты не робот

Использование монаха штата Хаскелл запаха кода?

Бог, я ненавижу термин "запах кода", но я не могу придумать ничего более точного.

Я разрабатываю язык высокого уровня и компилятор для Whitespace в свое свободное время, чтобы узнать о построении компилятора, и функциональное программирование (компилятор записывается в Haskell).

Во время фазы генерации кода компилятора я должен поддерживать данные "state" -ish, когда я пересекаю дерево синтаксиса. Например, при компиляции операторов управления потоком мне нужно создать уникальные имена для ярлыков, на которые можно перейти (метки, созданные с помощью счетчика, который был передан, обновлен и возвращен, а старое значение счетчика никогда не должно использоваться снова). Другим примером является то, что когда я сталкиваюсь с строковыми литералами в синтаксическом дереве, они должны быть постоянно преобразованы в переменные кучи (в Whitespace строки лучше всего хранятся в куче). В настоящее время я завершаю весь модуль генерации кода в государственной монаде, чтобы справиться с этим.

Мне сказали, что писать компилятор - это проблема, хорошо подходящая для функциональной парадигмы, но я нахожу, что я ее проектирую точно так же, как я бы ее проектировал на C (вы действительно можете писать C в любом язык - даже Haskell w/state monads).

Я хочу научиться думать в Haskell (скорее, в функциональной парадигме) - не в C с синтаксисом Haskell. Должен ли я действительно попытаться устранить/свести к минимуму использование государственной монады или это законный функциональный "шаблон дизайна"?

4b9b3361

Ответ 1

Я бы сказал, что состояние вообще не является запахом кода, если оно остается маленьким и хорошо контролируемым.

Это означает, что использование монад, таких как State, ST или настраиваемых, или просто наличие структуры данных, содержащей данные состояния, которые вы передаете в нескольких местах, не так уж плохо. (На самом деле, монады - это просто помощь в выполнении именно этого!) Однако, имея состояние, которое идет повсеместно (да, это означает, что вы, МО-монада!) - плохой запах.

Ярким примером этого была моя команда, которая работала над нашей записью для ICFP Programming Contest 2009 (код доступен в git://git.cynic.net/Haskell/МКВП-конкурс-2009). Мы получили несколько различных модульных деталей:

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

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

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

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

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

В одном ответе говорится: "Не используйте монады". С моей точки зрения, это точно в обратном направлении. Монады - это структура управления, которая, среди прочего, может помочь вам свести к минимуму количество кода, который касается состояния. Если вы посмотрите на монадические синтаксические анализаторы в качестве примера, состояние анализа (т.е. Анализируемый текст, как далеко он попал к нему, любые накопленные предупреждения и т.д.) Должны проходить через каждый комбинатор, используемый в синтаксическом анализаторе, Но будет только несколько комбинаторов, которые фактически манипулируют государством напрямую; все остальное использует одну из этих нескольких функций. Это позволяет вам четко видеть и в одном месте все небольшое количество кода, которое может изменить состояние, и более легко понять, как его можно изменить, что снова облегчает работу.

Ответ 2

Я написал несколько компиляторов в Haskell, а государственная монада - разумное решение многих проблем компилятора. Но вы хотите сохранить его абстрактным - не делайте очевидным, что вы используете монаду.

Вот пример из компилятора Glasgow Haskell (который я не писал, я просто работаю с несколькими ребрами), где мы строим графики потока управления. Вот основные способы создания графиков:

empyGraph    :: Graph
mkLabel      :: Label -> Graph
mkAssignment :: Assignment -> Graph  -- modify a register or memory
mkTransfer   :: ControlTransfer -> Graph   -- any control transfer
(<*>)        :: Graph -> Graph -> Graph

Но, как вы обнаружили, поддержка поставок уникальных ярлыков в лучшем случае утомительна, поэтому мы также предоставляем эти функции:

withFreshLabel :: (Label -> Graph) -> Graph
mkIfThenElse :: (Label -> Label -> Graph) -- branch condition
             -> Graph   -- code in the 'then' branch
             -> Graph   -- code in the 'else' branch 
             -> Graph   -- resulting if-then-else construct

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

Государственная монада скрыта под ней; хотя это не подвержено клиенту, определение Graph выглядит примерно так:

type Graph = RealGraph -> [Label] -> (RealGraph, [Label])

или более точно

type Graph = RealGraph -> State [Label] RealGraph
  -- a Graph is a monadic function from a successor RealGraph to a new RealGraph

С государственной монадой, скрытой за слоем абстракции, она не вонючая вообще!

Ответ 3

Вы посмотрели Атрибуты грамматики (AG)? (Подробнее о wikipedia и статья в Monad Reader)?

С AG вы можете добавлять атрибуты к дереву синтаксиса. Эти атрибуты разделены в синтезированных и унаследованных атрибутах.

Синтезированные атрибуты - это вещи, которые вы генерируете (или синтезируете) из своего дерева синтаксиса, это может быть сгенерированный код или все комментарии или что-то еще, что вас интересует.

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

В Университете Утрехта мы используем Система грамматики атрибутов (UUAGC) для написания компиляторов. Это предварительный процессор, который генерирует код haskell (.hs files) из предоставленных файлов .ag.


Хотя, если вы все еще учитесь Haskell, то, возможно, сейчас не время начинать изучать еще один слой абстракции над этим.

В этом случае вы можете вручную написать тип кода, который генерирует для вас грамматики, например:

data AbstractSyntax = Literal Int | Block AbstractSyntax
                    | Comment String AbstractSyntax

compile :: AbstractSyntax -> [Label] -> (Code, Comments)
compile (Literal x) _      = (generateCode x, [])
compile (Block ast) (l:ls) = let (code', comments) = compile ast ls
                             in (labelCode l code', comments)
compile (Comment s ast) ls = let (code, comments') = compile ast ls
                             in (code, s : comments')

generateCode :: Int -> Code
labelCode :: Label -> Code -> Code

Ответ 5

Я не думаю, что использование State Monad является запахом кода, когда он использовался для моделирования состояния.

Если вам нужно передать состояние через свои функции, вы можете сделать это явно, взяв состояние в качестве аргумента и вернув его в каждую функцию. Государственная Монада предлагает хорошую абстракцию: она передает государство для вас и предоставляет множество полезных функций для объединения функций, требующих состояния. В этом случае использование State Monad (или Applicatives) не является запахом кода.

Однако, если вы используете Государственную Монаду для подражания императивному стилю программирования в то время как функционального решения было бы достаточно, вы просто усложняете ситуацию.

Ответ 6

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

Ответ 7

Ну, не используйте монады. Сила функционального программирования - это чистота функций и их повторное использование. Там в этой статье один мой профессор однажды написал, и он один из парней, которые помогли построить Haskell.

Бумага называется " Почему функциональное программирование имеет значение?", я предлагаю вам прочитать его. Это хорошо прочитано.

Ответ 8

будьте осторожны с терминологией здесь. Государство само по себе плохое; функциональные языки имеют состояние. Что такое "запах кода", когда вы обнаруживаете, что хотите присвоить значения переменных и изменить их.

Конечно, государственная монада Haskell существует именно по этой причине - как с I/O, это позволяет вам делать небезопасные и ненадежные вещи в ограниченном контексте.

Итак, да, это, вероятно, запах кода.