Какую категорию ошибок предотвращают монады?

Насколько я понимаю, для монады 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 для регистрации и обработки исключений.

Другие вопросы по тегам