Тип Переменная Расположение в Трансформаторах
Рассмотрим State
тип - или хотя бы упрощенная версия:
newtype State s a = State { runState :: s -> (a, s) }
Теперь, скажем, мы хотим получить StateT
монадный трансформатор. transformers
определяет это следующим образом:
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
Здесь m
был размещен справа от стрелки функции, но вне кортежа. Однако, если мы не знали правильный ответ, мы могли бы вместо этого поставить m
где-нибудь еще:
newtype StateT s m a = StateT { runStateT :: m (s -> ( a, s)) }
newtype StateT s m a = StateT { runStateT :: s -> (m a, s) }
Очевидно, что версия в transformers
правильно, но почему? В более общем смысле, как узнать, куда поместить переменную типа для "внутренней" монады при определении преобразователя монад? Обобщая еще больше, есть ли подобное правило для comonad трансформаторов?
1 ответ
Я думаю, что разницу можно легко понять, когда m ~ IO
:
s -> IO (a, s)
тип действия, которое может прочитать текущее состояние s
выполнить IO в зависимости от этого (например, распечатать текущее состояние, прочитать строку от пользователя), а затем произвести оба новых состояния s
и возвращаемое значение a
,
Вместо:
IO (s -> (a, s))
тип действия, которое немедленно выполняет IO, не зная текущего состояния. После того, как весь IO закончен, он возвращает чистую функцию, отображающую старое состояние в новое состояние и возвращаемое значение.
Это похоже на предыдущий тип, поскольку новое состояние и возвращаемое значение могут зависеть как от предыдущего состояния, так и от ввода-вывода. Однако ввод-вывод не может зависеть от текущего состояния: например, печать текущего состояния запрещена.
Вместо,
s -> (IO a, s)
тип действия, которое читает текущее состояние s
и затем выполняет ввод-вывод в зависимости от этого (например, печать текущего состояния, чтение строки от пользователя), а затем производит возвращаемое значение a
, Зависит от текущего состояния, бот не от ввода-вывода, создается новое состояние. Этот тип эффективно изоморфен паре функций (s -> IO a, s -> s)
,
Здесь IO может прочитать строку от пользователя и вывести возвращаемое значение a
в зависимости от этого, но новое состояние не может зависеть от этой линии.
Поскольку первый вариант является более общим, мы хотим, чтобы он был нашим преобразователем состояния.
Я не думаю, что есть "общее правило" для решения, куда поставить m
: это зависит от того, чего мы хотим достичь.