Декларативный стиль кода в Elixir
Принцип «Tell, Don’t Ask» — часто обсуждаемая тема в сообществах объектно-ориентированных языков. Его цель — побудить к использованию инкапсуляции, то есть предписанию объекту определённых действий вместо принятия решений на его состояния. С целью избежать плохого кода, как, например, здесь, нужно сделать так, чтобы вызывающая функция в явном виде давала команды независимо от состояния объекта.
Принцип «Tell, Don’t Ask» в Elixir
Является ли Elixir объектно-ориентированным языком? С точки зрения парадигмы, Elixir — язык функциональный: об этом говорят иммутабельность, сопоставление с образцом, функции, имеющие входные и выходные параметры, предназначенные для отправки сообщений «объектам». Что же означает принцип «Tell, Don’t Ask»?
Чтобы выяснить это, проведём некоторые смысловые параллели. Объект в ООП — это сущность, наделённая поведением (методы) и данными (состояние). В ФП основным объектом выступает функция, состояние которой хранится в различных структурах данных (в Elixir это Maps или Structs). Согласно указанному принципу, необходимо избежать того, чтобы вызывающая функция принимала решения на основе информации, полученной на основе имеющихся данных.
Рассмотрим нехарактерный для Elixir код и попробуем выяснить, что в нём можно исправить.
С функцией Game.Lobby.add_player/2
явно что-то не так. Очевидна излишняя функциональная зависимость от типа player и структуры %Game.Player{}
. И почему функция Game.Player.generate_id/0
объявлена публично? Похоже, в функции Game.Lobby.add_player/2
всё внимание стоит обратить только на её структуру (последние две строки тела функции).
Вместо того, чтобы заставлять функцию Game.Lobby.add_player/2
создавать игрока, генерировать id и т. п., поручим модулю Game.Player
следующие действия:
Таким образом, за процесс создания игрока и генерацию структуры вместо функции Game.Lobby.add_player/2
теперь отвечает модуль Game.Player
Пишем декларативно
Переместив логику создания игрока из Game.Lobby.add_player/2
в Game.Player.new/1
, можно осуществить необходимое действие путём вызова единственной функции на основе входящих данных. Это и есть то поведение, необходимое для создания %Game.Player{}
.
Особенно важно это учитывать при использовании пайп-оператора, который отлично подходит для преобразования данных.
Принцип «Tell, don’t ask» — один из способов побудить разработчиков писать в декларативном стиле. При императивном подходе сначала задаются вопросы, а потом принимаются решения; при декларативном — даются команды и ожидается их выполнение.