Какую категорию ошибок предотвращают монады?
Насколько я понимаю, для монады do каждый шаг имеет продолжение и закрытие.
Этот автор пишет:
Мы видели, что чистота, сильные типы и монады могут:
...
- Предотвратите ошибки, которые могут возникнуть из-за путаницы между различными фазами выполнения.
Мой вопрос: какова категория ошибок, которые предотвращают монады?
1 ответ
Допустим, вы написали алгоритм, который должен получить обратный вызов. Вы не знаете, что обратный вызов захочет сделать или на что он способен. Самый общий способ принять такой обратный вызов - это получить такую функцию:
Monad m => a -> m b
Это дает полную свободу вашему абоненту (который может выбрать любой m
это монада), отказывая в такой свободе вашей библиотеке. Это предотвращает введение побочных эффектов в чистую библиотеку, в то же время позволяя возникать побочным эффектам, если того пожелает вызывающий объект.
Я использовал этот шаблон раньше в чистом распределителе регистров. В этой библиотеке мне никогда не требовались эффекты, но я хотел, чтобы пользователь мог использовать свои собственные эффекты (например, State) для создания новых блоков и инструкций перемещения.
Разделение эффектов
Как обычные типы дают вам возможность различать данные, так и монады дают вам возможность различать эффекты.
Эликсир
Вот пример в Elixir
, который является почти чистым функциональным языком поверх Erlang. Этот пример взят из реальной ситуации, которая очень часто случается на моей работе.
def handle_call(:get_config_foo, _, state) do:
{:reply, state.foo, state}
end
def handle_call(:get_bar, _, state) do:
{:reply, state.bar, state}
end
def handle_call({:set_bar, bar}, _, state) do:
{:reply, :ok, %{state | bar: bar}}
end
Это определяет API GenServer, который является небольшим узлом Erlang, который содержит некоторые state
и позволяет вам запросить его, а также изменить его. Первый звонок, :get_config_foo
читает неизменный параметр конфигурации. Второй набор звонков, :get_bar
а также {:set_bar, bar}
, получает и устанавливает изменяемую переменную состояния.
Как бы мне хотелось, чтобы здесь были монады, чтобы предотвратить следующую ошибку:
def handle_call({:set_bar, bar}, _, state) do:
{:reply, :ok, %{state | foo: bar}}
end
Вы можете заметить ошибку? Ну, я просто написал значение только для чтения. Ничто в Эликсире не мешает этому. Вы не можете пометить некоторые части вашего состояния GenServer только для чтения, а другие - для чтения и записи.
Haskell: читатель и государство
В Haskell вы можете использовать разные монады, чтобы определять различные виды эффектов. Вот только для чтения состояние (Reader
) и состояние чтения-записи:
data Reader r a = Reader (r -> a)
data State s a = State (s -> (a, s))
Reader
позволяет получить доступ к состоянию конфигурации r
вернуть какое-то значение a
, State
позволяет читать состояние, возвращать некоторое значение и изменять состояние. Обе монады, что, по сути, означает, что вы можете последовательно соединять эти обращения к состоянию императивным способом. В Reader
сначала вы можете прочитать один параметр конфигурации, а затем (на основе этого первого параметра) прочитать другой параметр. В State
Вы можете прочитать состояние, а затем (в зависимости от того, что вы прочитали) изменить его в дальнейшем. Но вы никогда не сможете изменить состояние в Reader
пока вы выполняете это.
Детерминированные эффекты
Позвольте мне повторить это. Привязка нескольких звонков к Reader
уверяет вас, что вы никогда не сможете изменить состояние читателя между ними. Если у вас есть getConfigFoo1 :: Reader Config Foo1
а также getConfigFoo2 :: Foo1 -> Reader Config Foo2
и вы делаете getAllConfig = getConfigFoo1 >>= getConfigFoo2
, тогда у вас есть уверенность, что оба запроса будут выполняться на одном и том же Config
, Эликсир не имеет этой функции и позволяет вышеупомянутой ошибке остаться незамеченной.
Другие эффекты, где это полезно Writer
(состояние только для записи, например, ведение журнала) и Either
(Обработка исключений). Когда у тебя есть Writer
вместо Reader
или же State
, вы можете быть уверены, что ваше состояние когда-либо только добавляется. Когда у тебя есть Either
Вы точно знаете тип исключений, которые могут возникнуть. Это все намного лучше, чем при использовании IO
для регистрации и обработки исключений.