Как мне построить систему, которая имеет все следующее :
- Использование чистых функций с неизменяемыми объектами.
- Передайте в функцию только те данные, которые ей нужны, не более (то есть, нет большого объекта состояния приложения)
- Избегайте слишком большого количества аргументов для функций.
- Избегайте необходимости создавать новые объекты только с целью упаковки и распаковки параметров в функции, просто чтобы избежать слишком большого количества параметров, передаваемых в функции. Если я собираюсь упаковать несколько элементов в функцию как один объект, я хочу, чтобы этот объект был владельцем этих данных, а не что-то созданное временно
Мне кажется, что государственная монада нарушает правило № 2, хотя это не очевидно, потому что она пронизана монадой.
У меня есть ощущение, что мне нужно как-то использовать линзы, но очень мало написано об этом для нефункциональных языков.
Фон
В качестве упражнения я преобразовываю одно из моих существующих приложений из объектно-ориентированного стиля в функциональный стиль. Первое, что я пытаюсь сделать, - это сделать как можно больше внутреннего ядра приложения.
Одна вещь, которую я слышал, состоит в том, что как управлять «состоянием» на чисто функциональном языке, и это то, что, как я полагаю, выполняется монадами состояний, - это то, что логически вы называете чистую функцию, «передавая состояние «Мир как есть», затем, когда функция возвращается, она возвращает вам состояние мира, как оно изменилось.
Чтобы проиллюстрировать, как вы можете сделать «привет мир» чисто функциональным способом, вы можете передать в свою программу это состояние экрана и получить обратно состояние экрана с напечатанным «привет миром». Технически, вы делаете вызов чистой функции, и никаких побочных эффектов нет.
Исходя из этого, я просмотрел свое приложение и: 1. Сначала поместил все состояние своего приложения в один глобальный объект (GameState) 2. Во-вторых, я сделал GameState неизменным. Вы не можете это изменить. Если вам нужно изменить, вы должны построить новое. Я сделал это, добавив конструктор копирования, который необязательно принимает одно или несколько измененных полей. 3. Каждому приложению я передаю GameState в качестве параметра. Внутри функции, после того, как она делает то, что собирается, она создает новый GameState и возвращает его.
У меня чисто функциональное ядро и внешний цикл, который передает GameState в основной рабочий цикл приложения.
Мой вопрос:
Теперь моя проблема в том, что GameState содержит около 15 различных неизменяемых объектов. Многие из функций на самом низком уровне работают только с некоторыми из этих объектов, например, ведение счета. Итак, допустим, у меня есть функция, которая вычисляет счет. Сегодня GameState передается этой функции, которая изменяет счет путем создания нового GameState с новым счетом.
Что-то в этом кажется неправильным. Функция не нуждается во всей GameState. Ему просто нужен объект Score. Поэтому я обновил его, чтобы передать в Счет и возвращать только Счет.
Это, казалось, имело смысл, поэтому я пошел дальше с другими функциями. Некоторые функции требуют, чтобы я передавал 2, 3 или 4 параметра из GameState, но, поскольку я использовал шаблон во всем внешнем ядре приложения, я передаю все больше и больше состояния приложения. Например, в верхней части цикла рабочего процесса я бы вызвал метод, который вызвал бы метод, который вызвал бы метод и т. Д., Вплоть до того места, где вычисляется оценка. Это означает, что текущий счет проходит через все эти слои только потому, что функция в самом низу собирается вычислить счет.
Теперь у меня есть функции с десятками параметров. Я мог бы поместить эти параметры в объект, чтобы уменьшить количество параметров, но тогда я хотел бы, чтобы этот класс был главным местоположением состояния приложения состояния, а не объектом, который просто создается во время вызова просто для того, чтобы избежать передачи в несколько параметров, а затем распаковать их.
Так что теперь мне интересно, проблема в том, что мои функции вложены слишком глубоко. Это результат желания иметь маленькие функции, поэтому я делаю рефакторинг, когда функция становится большой, и делю ее на несколько меньших функций. Но при этом создается более глубокая иерархия, и все, что передается во внутренние функции, необходимо передавать во внешнюю функцию, даже если внешняя функция не работает с этими объектами напрямую.
Казалось, что просто прохождение GameState по пути избежало этой проблемы. Но я вернулся к первоначальной проблеме передачи большего количества информации в функцию, чем требуется функции.