Я бы сказал, что состояние в общем-то не является запахом кода, если оно остается маленьким и хорошо контролируемым.
Это означает, что использование монад, таких как State, ST или пользовательские, или просто наличие структуры данных, содержащей данные о состоянии, которые вы передаете в несколько мест, не является плохой вещью. (На самом деле, монады - просто помощь в выполнении именно этого!) Однако иметь состояние, которое происходит повсюду (да, это означает, что вы, IO монада!) - это неприятный запах.
Довольно наглядный пример этого был, когда моя команда работала над нашей заявкой на ICFP Programming Contest 2009 (код доступен по адресу git: //git.cynic.net/haskell/icfp- конкурс-2009). Мы закончили с несколькими различными модульными частями к этому:
- VM: виртуальная машина, на которой запущена программа моделирования
- Контроллеры: несколько различных наборов подпрограмм, которые читают выходные данные симулятора и генерируют новые управляющие входы
- Решение: создание файла решения на основе выходных данных контроллеров
- Визуализаторы: несколько различных наборов подпрограмм, которые читают как входные, так и выходные порты и генерируют какую-то визуализацию или журнал того, что происходило в ходе симуляции.
Каждый из них имеет свое собственное состояние, и все они взаимодействуют по-разному через входные и выходные значения виртуальной машины. У нас было несколько разных контроллеров и визуализаторов, каждый из которых имел свой собственный тип состояния.
Ключевым моментом здесь было то, что внутренности любого конкретного состояния были ограничены их собственными конкретными модулями, и каждый модуль ничего не знал даже о существовании состояния для других модулей. Любой конкретный набор кода и данных с состоянием обычно составлял всего несколько десятков строк с несколькими элементами данных в состоянии.
Все это было склеено в одну маленькую функцию из примерно дюжины строк, которые не имели доступа к внутренним частям какого-либо из состояний, и которые просто называли правильные вещи в правильном порядке, когда они проходили через симуляцию, и передавались очень ограниченное количество внешней информации для каждого модуля (конечно, вместе с предыдущим состоянием модуля).
Когда состояние используется таким ограниченным образом, а система типов не позволяет вам непреднамеренно изменить его, с ним довольно легко справиться. Это одна из красот Хаскелла, которая позволяет вам делать это.
Один ответ говорит: «Не используйте монады». С моей точки зрения, это как раз наоборот. Монады - это структура управления, которая, помимо прочего, может помочь вам минимизировать объем кода, который касается состояния. Если вы посмотрите на монадические парсеры в качестве примера, то состояние синтаксического анализа (т. Е. Анализируемый текст, как далеко он дошел до него, все накопленные предупреждения и т. Д.) Должно проходить через каждый комбинатор, используемый в парсере. , Тем не менее, будет лишь несколько комбинаторов, которые фактически манипулируют государством напрямую; все остальное использует одну из этих нескольких функций. Это позволяет вам ясно видеть и в одном месте весь небольшой объем кода, который может изменить состояние, и более легко рассуждать о том, как его можно изменить, снова облегчая работу с ним.