Как всегда, терминология, которую используют люди, не совсем последовательна. Есть множество идей, вдохновленных монадами, но, строго говоря, не совсем. Термин «индексированная монада» является одним из числа (включая «монаду» и «параметризованную монаду» (имя Атки для них)) терминов, используемых для характеристики одного такого понятия. (Еще одно такое понятие, если вам интересно, - это «монада параметрических эффектов» Кацуматы, индексируемая моноидом, где return индексируется нейтрально, а привязка накапливается в своем индексе.)
Прежде всего, проверим виды.
IxMonad (m :: state -> state -> * -> *)
То есть тип «вычисления» (или «действия», если хотите, но я буду придерживаться «вычисления»), выглядит так:
m before after value
где before, after :: state
и value :: *
. Идея состоит в том, чтобы уловить средства для безопасного взаимодействия с внешней системой, которая имеет некоторое предсказуемое понятие состояния. Тип вычисления сообщает вам, в каком состоянии он должен быть before
запущен, в каком состоянии он будет выполняться after
и (как с обычными монадами *
), какой тип value
вычислений производит.
Обычные кусочки и части по- *
своему похожи на монаду, а state
-мы - на игру в домино.
ireturn :: a -> m i i a -- returning a pure value preserves state
ibind :: m i j a -> -- we can go from i to j and get an a, thence
(a -> m j k b) -- we can go from j to k and get a b, therefore
-> m i k b -- we can indeed go from i to k and get a b
Таким образом, порожденное понятие "стрелы Клейсли" (функция, производящая вычисления) имеет вид
a -> m i j b -- values a in, b out; state transition i to j
и мы получаем композицию
icomp :: IxMonad m => (b -> m j k c) -> (a -> m i j b) -> a -> m i k c
icomp f g = \ a -> ibind (g a) f
и, как всегда, законы точно это гарантируют ireturn
и icomp
дают нам категорию
ireturn `icomp` g = g
f `icomp` ireturn = f
(f `icomp` g) `icomp` h = f `icomp` (g `icomp` h)
или, в комедийной подделке C / Java / что угодно,
g(); skip = g()
skip; f() = f()
{g(); h()}; f() = h(); {g(); f()}
Зачем беспокоиться? Смоделировать «правила» взаимодействия. Например, вы не можете извлечь DVD, если его нет на диске, и вы не можете вставить DVD в дисковод, если он уже есть. Так
data DVDDrive :: Bool -> Bool -> * -> * where -- Bool is "drive full?"
DReturn :: a -> DVDDrive i i a
DInsert :: DVD -> -- you have a DVD
DVDDrive True k a -> -- you know how to continue full
DVDDrive False k a -- so you can insert from empty
DEject :: (DVD -> -- once you receive a DVD
DVDDrive False k a) -> -- you know how to continue empty
DVDDrive True k a -- so you can eject when full
instance IxMonad DVDDrive where -- put these methods where they need to go
ireturn = DReturn -- so this goes somewhere else
ibind (DReturn a) k = k a
ibind (DInsert dvd j) k = DInsert dvd (ibind j k)
ibind (DEject j) k = DEject j $ \ dvd -> ibind (j dvd) k
Имея это место, мы можем определить "примитивные" команды
dInsert :: DVD -> DVDDrive False True ()
dInsert dvd = DInsert dvd $ DReturn ()
dEject :: DVDrive True False DVD
dEject = DEject $ \ dvd -> DReturn dvd
из которых собираются другие с помощью ireturn
и ibind
. Теперь я могу написать (заимствование - do
обозначение)
discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dvd' <- dEject; dInsert dvd ; ireturn dvd'
но не физически невозможное
discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dInsert dvd; dEject -- ouch!
В качестве альтернативы можно напрямую определить свои примитивные команды
data DVDCommand :: Bool -> Bool -> * -> * where
InsertC :: DVD -> DVDCommand False True ()
EjectC :: DVDCommand True False DVD
а затем создать экземпляр универсального шаблона
data CommandIxMonad :: (state -> state -> * -> *) ->
state -> state -> * -> * where
CReturn :: a -> CommandIxMonad c i i a
(:?) :: c i j a -> (a -> CommandIxMonad c j k b) ->
CommandIxMonad c i k b
instance IxMonad (CommandIxMonad c) where
ireturn = CReturn
ibind (CReturn a) k = k a
ibind (c :? j) k = c :? \ a -> ibind (j a) k
Фактически, мы сказали, что такое примитивные стрелки Клейсли (что такое одно «домино»), а затем построили над ними подходящее понятие «вычислительной последовательности».
Обратите внимание, что для каждой индексированной монады m
«диагональ без изменений» m i i
является монадой, но в целом таковой m i j
не является. Более того, значения не индексируются, а вычисления индексируются, поэтому индексированная монада - это не просто обычная идея монады, созданной для какой-то другой категории.
Теперь посмотрим еще раз на тип стрелки Клейсли.
a -> m i j b
Мы знаем, что должны быть в состоянии, i
чтобы начать, и предсказываем, что любое продолжение начнется с состояния j
. Мы много знаем об этой системе! Это не рискованная операция! Когда мы вставляем dvd в привод, он входит! Привод DVD не может сказать, в каком состоянии находится после каждой команды.
Но в общем, при взаимодействии с миром это не так. Иногда вам может потребоваться отказаться от некоторого контроля и позволить миру делать то, что ему нравится. Например, если вы сервер, вы можете предложить своему клиенту выбор, и состояние вашего сеанса будет зависеть от того, что он выберет. Операция сервера «выбор предложения» не определяет результирующее состояние, но сервер все равно должен иметь возможность продолжить. Это не «примитивная команда» в указанном выше смысле, поэтому индексированные монады не такой хороший инструмент для моделирования непредсказуемого сценария.
Какой инструмент лучше?
type f :-> g = forall state. f state -> g state
class MonadIx (m :: (state -> *) -> (state -> *)) where
returnIx :: x :-> m x
flipBindIx :: (a :-> m b) -> (m a :-> m b) -- tidier than bindIx
Страшное печенье? Не совсем по двум причинам. Во- первых, это выглядит скорее как то , что монада, потому что это монада, но над (state -> *)
чем *
. Во-вторых, если вы посмотрите на тип стрелки Клейсли,
a :-> m b = forall state. a state -> m b state
вы получаете тип вычислений с предусловием a
и постусловием b
, как в старой доброй логике Хоара. Утверждениям в программной логике потребовалось менее полувека, чтобы пересечь соответствие Карри-Ховарда и стать типами Haskell. Тип returnIx
говорит: «Вы можете достичь любого выполненного постусловия, просто ничего не делая», что является правилом логики Хоара для «пропуска». Соответствующая композиция - это правило логики Хоара для ";".
Давайте закончим, рассмотрев тип bindIx
, добавив в него все квантификаторы.
bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i
Эти forall
s имеют противоположную полярность. Мы выбираем начальное состояние i
и вычисление, с которого можно начинать i
, с постусловием a
. Мир выбирает любое промежуточное состояние, которое j
ему нравится, но он должен предоставить нам свидетельство того, что постусловие b
выполняется, и из любого такого состояния мы можем продолжить, чтобы b
удержаться. Итак, последовательно мы можем достичь состояния b
из состояния i
. Освободив контроль над состояниями «после», мы можем моделировать непредсказуемые вычисления.
Оба IxMonad
и MonadIx
полезны. Обе модели достоверности интерактивных вычислений в отношении изменения состояния, предсказуемого и непредсказуемого, соответственно. Предсказуемость ценится, когда ее можно получить, но непредсказуемость иногда является фактом жизни. Надеюсь, что этот ответ дает некоторое представление о том, что такое индексированные монады, предсказывая как то, когда они начнут быть полезными, так и когда они прекратятся.
True
/False
values в качестве аргументов типаDVDDrive
? Это какое-то расширение, или здесь действительно вводятся логические значения?DataKinds
расширения и в языках с зависимой типизацией ... ну, в этом вся суть.MonadIx
, возможно, привести примеры? Это лучше теоретически или лучше для практического применения?RebindableSyntax
расширением. Было бы неплохо упомянуть и о других необходимых расширениях, например о вышеупомянутомDataKinds
Я знаю как минимум три способа определить индексированную монаду.
Я буду называть эти варианты индексированными монадами а-ля X , где X простирается до компьютерных ученых Боба Этки, Конора Макбрайда и Доминика Орчарда, как я склонен думать о них. Части этих конструкций имеют гораздо более длинную, яркую историю и более приятные интерпретации с помощью теории категорий, но я впервые узнал о них, связанных с этими именами, и я стараюсь, чтобы этот ответ не стал слишком эзотерическим.
Аткей
Стиль индексированной монады Боба Атки заключается в работе с двумя дополнительными параметрами для работы с индексом монады.
Таким образом, вы получите определения, которые люди использовали в других ответах:
Мы также можем определить индексированные комонады а-ля Atkey. Я действительно получаю много пользы от тех, что есть в
lens
кодовой базе .Макбрайд
Следующая форма индексированной монады - это определение Конора Макбрайда из его статьи «Клейсли-стрелы возмутительной удачи» . Вместо этого он использует единственный параметр для индекса. Это придает определению индексированной монады довольно умную форму.
Если мы определим естественное преобразование, используя параметричность следующим образом
тогда мы можем записать определение Макбрайда как
Это выглядит совсем иначе, чем у Атки, но это больше похоже на обычную монаду, вместо того, чтобы строить монаду
(m :: * -> *)
, мы строим ее(m :: (k -> *) -> (k -> *)
.Интересно, что вы можете фактически восстановить стиль индексированной монады Атки от Макбрайда, используя умный тип данных, который Макбрайд в своем неподражаемом стиле предпочитает читать как «по ключу».
Теперь ты можешь это понять
который расширяется до
может быть вызван только тогда, когда j = i, и тогда внимательное чтение
ibind
может вернуть то же самое, что и Atkeyibind
. Вам необходимо передать эти (: =) структуры данных, но они восстанавливают мощь представления Atkey.С другой стороны, представление Атки недостаточно убедительно, чтобы восстановить все варианты использования версии Макбрайда. Власть была получена строго.
Еще одна приятная вещь заключается в том, что проиндексированная монада МакБрайда - это явно монада, это просто монада в другой категории функторов. Он работает с эндофункторами в категории функторов от
(k -> *)
до,(k -> *)
а не с категорией функторов от*
до*
.Забавное упражнение - выяснить, как выполнить преобразование Макбрайда в Аткей для индексированных комонад . Я лично использую тип данных «At» для конструкции «at key» в статье Макбрайда. Я действительно подошел к Бобу Этки на ICFP 2013 и упомянул, что вывернул его наизнанку, превратив его в «пальто». Он выглядел явно обеспокоенным. Эта линия лучше разыгралась в моей голове. знак равно
Фруктовый сад
Наконец, третий, гораздо менее часто упоминаемый заявитель на имя «индексированной монады», связан с Домиником Орчардом, где вместо этого он использует моноид уровня типа для объединения индексов. Вместо того, чтобы вдаваться в подробности конструкции, я просто сделаю ссылку на этот доклад:
https://github.com/dorchard/effect-monad/blob/master/docs/ixmonad-fita14.pdf
источник
ibind
»: введите псевдоним типаAtkey m i j a = m (a := j) i
. Используя это какm
в определении Atkey, восстанавливаются две сигнатуры, которые мы ищем:ireturnAtkin :: a -> m (a := i) i
иibindAtkin :: m (a := j) i -> (a -> m (b := k) j) -> m (b := k) i
. Первый из них получают композиции:ireturn . V
. Второй: (1) построение функцииforall j. (a := j) j -> m (b := k) j
путем сопоставления с образцом, а затем передача восстановленного значенияa
второму аргументуibindAtkin
.В качестве простого сценария предположим, что у вас есть монада состояний. Тип состояния является сложным и большим, но все эти состояния можно разделить на два набора: красные и синие состояния. Некоторые операции в этой монаде имеют смысл, только если текущее состояние - синее состояние. Среди них некоторые сохранят состояние синим (
blueToBlue
), а другие сделают состояние красным (blueToRed
). В обычной монаде мы могли бы написатьвызывает ошибку времени выполнения, поскольку второе действие ожидает синего состояния. Мы хотели бы предотвратить это статически. Индексированная монада выполняет эту цель:
Ошибка типа возникает из-за того, что второй индекс
blueToRed
(Red
) отличается от первого индексаblueToBlue
(Blue
).В качестве другого примера, с помощью индексированных монад вы можете разрешить монаде состояния изменять тип своего состояния, например, вы могли бы иметь
Вы можете использовать описанное выше для создания состояния, которое представляет собой статически типизированный гетерогенный стек. Операции будут иметь тип
В качестве другого примера предположим, что вам нужна ограниченная монада ввода-вывода, которая не разрешает доступ к файлам. Вы можете использовать, например,
Таким образом, для действия, имеющего тип
IO ... NoAccess ()
, статически гарантируется отсутствие доступа к файлам. Вместо этого действие типаIO ... FilesAccessed ()
может получить доступ к файлам. Наличие индексированной монады означало бы, что вам не нужно создавать отдельный тип для ограниченного ввода-вывода, что потребовало бы дублирования каждой функции, не связанной с файлом, в обоих типах ввода-вывода.источник
Индексированная монада - это не конкретная монада, как, например, монада состояния, а своего рода обобщение концепции монады с дополнительными параметрами типа.
В то время как "стандартное" монадическое значение имеет тип,
Monad m => m a
значение в индексированной монаде будетIndexedMonad m => m i j a
wherei
иj
являются индексными типами, так чтоi
это тип индекса в начале монадического вычисления иj
в конце вычисления. В некотором смысле, вы можете думать об этомi
как о типе ввода иj
как о типе вывода.Используя в
State
качестве примера, с состоянием вычисленияState s a
сохраняет состояние типа вs
течение вычисления и возвращает результат типаa
. Индексированная версияIndexedState i j a
- это вычисление с отслеживанием состояния, при котором состояние может измениться на другой тип во время вычисления. Начальное состояние имеет типi
и состояние, а конец вычисления имеет типj
.Использование индексированной монады вместо обычной монады необходимо редко, но в некоторых случаях ее можно использовать для кодирования более строгих статических гарантий.
источник
Может быть важно посмотреть, как индексирование используется в зависимых типах (например, в agda). Это может объяснить, как в целом помогает индексирование, а затем перенести этот опыт на монады.
Индексирование позволяет устанавливать отношения между конкретными экземплярами типов. Затем вы можете рассуждать о некоторых ценностях, чтобы установить, сохраняется ли эта связь.
Например (в agda) вы можете указать, что некоторые натуральные числа связаны с ними
_<_
, а тип указывает, какие числа они есть. Затем вы можете потребовать, чтобы некоторая функция получила свидетельствоm < n
, потому что только тогда функция работает правильно - и без предоставления такого свидетельства программа не будет компилироваться.В качестве другого примера, учитывая достаточную настойчивость и поддержку компилятора для выбранного вами языка, вы можете закодировать, что функция предполагает, что определенный список отсортирован.
Индексированные монады позволяют кодировать часть того, что делают системы зависимых типов, для более точного управления побочными эффектами.
источник