it-swarm.com.ru

Что такое индексированная монада?

Что такое индексируемая монада и мотивация для этой монады?

Я прочитал, что это помогает отслеживать побочные эффекты. Но подпись типа и документация никуда меня не ведут.

Что может быть примером того, как это может помочь отследить побочные эффекты (или любой другой действительный пример)?

94
Sibi

Как всегда, используемая людьми терминология не совсем последовательна. Есть множество вдохновленных монад, но строго говоря, не совсем понятий. Термин "индексированная монада" является одним из ряда (включая "монадская" и "параметризованная монада" (название Atkey для них)) терминов, используемых для характеристики одного такого понятия. (Другим таким понятием, если вам интересно, является "Монада параметрического эффекта" Кацуматы, индексируемая моноидом, где возвращение индексируется нейтрально, а связывание накапливается в его индексе.)

Прежде всего, давайте проверим виды.

IxMonad (m :: state -> state -> * -> *)

То есть тип "вычисления" (или "действие", если вы предпочитаете, но я буду придерживаться "вычисления"), выглядит как

m before after value

где before, after :: state и value :: *. Идея состоит в том, чтобы охватить средства безопасного взаимодействия с внешней системой, которая имеет некоторое предсказуемое представление о состоянии. Тип вычисления сообщает вам, какое состояние должно быть before, которое он выполняет, какое состояние будет after, которое он выполняет, и (как и в случае с обычными монадами над *), какой тип values генерирует вычисление.

Обычные фрагменты - это *- как монада, а 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()
{h(); g()}; 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, как в Good Old Hoare Logic. Утверждениям в логике программы понадобилось менее полувека, чтобы пересечь соответствие Карри-Ховарда и стать типами Хаскеля. Тип returnIx говорит: "Вы можете достичь любого постусловия, которое выполняется, просто ничего не делая", что является правилом Hoare Logic для "skip". Соответствующая композиция является правилом Hoare Logic для ";".

Давайте закончим, посмотрев на тип bindIx, добавив все квантификаторы.

bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i

Эти foralls имеют противоположную полярность. Мы выбираем начальное состояние i и вычисление, которое может начинаться с i, с постусловием a. Мир выбирает любое промежуточное состояние j, которое ему нравится, но он должен дать нам доказательство того, что постусловие b выполнено, и из любого такого состояния мы можем перейти к удержанию b. Таким образом, последовательно мы можем достичь условия b из состояния i. Освободив контроль над состояниями "после", мы можем моделировать непредсказуемые вычисления.

И IxMonad, и MonadIx полезны. Обе модели валидности интерактивных вычислений относительно изменяющегося состояния, предсказуемые и непредсказуемые, соответственно. Предсказуемость ценна, когда ее можно получить, но непредсказуемость иногда является фактом жизни. Надеемся, что тогда этот ответ дает некоторое представление о том, что такое индексированные монады, предсказывая, когда они начинают приносить пользу и когда они прекращаются.

118
pigworker

Есть как минимум три способа определения индексированной монады, которые я знаю.

Я буду ссылаться на эти опции как индексированные монады по типу X , где X охватывает компьютерных ученых Боба Атки, Конора Макбрайда и Доминика Орчарда. Вот как я склонен думать о них. Части этих конструкций имеют гораздо более выдающуюся историю и более приятные интерпретации через теорию категорий, но я впервые узнал о них, связанной с этими именами, и я стараюсь, чтобы этот ответ тоже не получал эзотерика.

Atkey

Стиль индексированной монады Боба Атки заключается в работе с 2 дополнительными параметрами для работы с индексом монады.

С этим вы получите определения, которые люди бросили в других ответах:

class IMonad m where
  ireturn  ::  a -> m i i a
  ibind    ::  m i j a -> (a -> m j k b) -> m i k b

Мы также можем определить индексированные комонады а-ля Atkey. Я на самом деле получаю много пользы от этих в кодовой базе lens .

McBride

Следующая форма индексируемой монады - это определение Конора Макбрайда из его статьи "Стрелы Крайсли Неистового счастья" . Вместо этого он использует один параметр для индекса. Это делает определение индексированной монады довольно умной формой.

Если мы определим естественное преобразование, используя параметричность следующим образом

type a ~> b = forall i. a i -> b i 

тогда мы можем записать определение Макбрайда как

class IMonad m where
  ireturn :: a ~> m a
  ibind :: (a ~> m b) -> (m a ~> m b)

Это выглядит совсем не так, как у Atkey, но больше похоже на обычную монаду, вместо того, чтобы строить монаду на (m :: * -> *), мы строим ее на (m :: (k -> *) -> (k -> *).

Интересно, что вы на самом деле можете восстановить стиль индексируемой монады из McBride, используя умный тип данных, который McBride в своем неповторимом стиле выбирает для чтения как "по ключу".

data (:=) :: a i j where
   V :: a -> (a := i) i

Теперь вы можете решить, что

ireturn :: IMonad m => (a := j) ~> m (a := j)

который расширяется до

ireturn :: IMonad m => (a := j) i -> m (a := j) i

может быть вызван только тогда, когда j = i, и тогда тщательное чтение ibind может вернуть вас так же, как ibind от Atkey. Вам необходимо обойти эти (: =) структуры данных, но они восстанавливают всю мощь представления Atkey.

С другой стороны, презентация Atkey недостаточно сильна, чтобы восстановить все варианты использования McBride. Сила была строго получена.

Еще одна приятная вещь - то, что индексируемая монада Макбрайда - это явно монада, это просто монада в другой категории функторов. Он работает над endofunctors для категории функторов от (k -> *) до (k -> *), а не для категории функторов от * до *.

Интересное упражнение - выяснить, как выполнить преобразование McBride в Atkey для индексированных комонад . Я лично использую тип данных "At" для конструкции "at key" в статье МакБрайда. Я на самом деле подошел к Бобу Атки на ICFP 2013 и сказал, что превратил его наизнанку, превратив в "Пальто". Он казался явно встревоженным. Линия разыгралась лучше в моей голове. знак равно

Фруктовый сад

Наконец, третий претендент на наименование "индексированная монада", на который гораздо реже ссылаются, связан с Домиником Орчардом, где он вместо этого использует моноид уровня типа, чтобы разбить вместе индексы. Вместо того, чтобы переходить к деталям конструкции, я просто добавлю ссылку на этот доклад:

http://www.cl.cam.ac.uk/~dao29/ixmonad/ixmonad-fita14.pdf

44
Edward KMETT

В качестве простого сценария предположим, что у вас есть государственная монада. Тип состояния является сложным большим, но все эти состояния можно разделить на два набора: красные и синие. Некоторые операции в этой монаде имеют смысл, только если текущее состояние является синим. Среди них некоторые сохранят состояние синим (blueToBlue), в то время как другие сделают состояние красным (blueToRed). В обычной монаде мы могли бы написать

blueToRed  :: State S ()
blueToBlue :: State S ()

foo :: State S ()
foo = do blueToRed
         blueToBlue

вызывая ошибку во время выполнения, так как второе действие ожидает синее состояние. Мы хотели бы предотвратить это статически. Индексированная монада выполняет эту цель:

data Red
data Blue

-- assume a new indexed State monad
blueToRed  :: State S Blue Red  ()
blueToBlue :: State S Blue Blue ()

foo :: State S ?? ?? ()
foo = blueToRed `ibind` \_ ->
      blueToBlue          -- type error

Ошибка типа вызвана тем, что второй индекс blueToRed (Red) отличается от первого индекса blueToBlue (Blue).

В качестве другого примера, с помощью индексированных монад вы можете разрешить монаде состояния изменять тип своего состояния, например, ты мог бы иметь

data State old new a = State (old -> (new, a))

Вы можете использовать вышеупомянутое, чтобы построить состояние, которое является статически типизированным гетерогенным стеком. Операции будут иметь тип

Push :: a -> State old (a,old) ()
pop  :: State (a,new) new a

В качестве другого примера, предположим, что вы хотите ограниченную монаду IO, которая не разрешает доступ к файлу. Вы можете использовать, например,.

openFile :: IO any FilesAccessed ()
newIORef :: a -> IO any any (IORef a)
-- no operation of type :: IO any NoAccess _

Таким образом, действие, имеющее тип IO ... NoAccess (), статически гарантировано не имеет доступа к файлу. Вместо этого действие типа IO ... FilesAccessed () может обращаться к файлам. Наличие индексированной монады означает, что вам не нужно создавать отдельный тип для ограниченного ввода-вывода, что потребует дублирования каждой не связанной с файлом функции в обоих типах IO.

23
chi

Индексированная монада - это не конкретная монада, как, например, монада состояний, а своего рода обобщение концепции монады с дополнительными параметрами типа.

В то время как "стандартное" монадическое значение имеет тип Monad m => m a, значение в индексированной монаде будет IndexedMonad m => m i j a, где i и j являются типами индекса, так что i является типом индекса в начале монадического вычисления и j в конце вычисление. В некотором смысле, вы можете думать о i как о типе ввода и j как о типе вывода.

Используя State в качестве примера, вычисление с состоянием State s a поддерживает состояние типа s на протяжении всего вычисления и возвращает результат типа a. Индексированная версия IndexedState i j a - это вычисление с состоянием, когда состояние может измениться на другой тип во время вычисления. Начальное состояние имеет тип i и состояние, а конец вычисления имеет тип j.

Использование индексированной монады над обычной монадой редко требуется, но в некоторых случаях ее можно использовать для кодирования более строгих статических гарантий.

17
shang

Может быть важно взглянуть, как индексация используется в зависимых типах (например, в agda). Это может объяснить, как индексирование помогает в целом, а затем преобразовать этот опыт в монады.

Индексирование позволяет устанавливать отношения между конкретными экземплярами типов. Затем вы можете подумать о некоторых значениях, чтобы установить, сохраняются ли эти отношения.

Например (в agda) вы можете указать, что некоторые натуральные числа связаны с _<_, а тип сообщает, какие они числа. Затем вы можете потребовать, чтобы какой-то функции был предоставлен свидетель m < n, потому что только тогда функция будет работать правильно - и без предоставления такого свидетеля программа не будет компилироваться.

В качестве другого примера, учитывая достаточную настойчивость и поддержку компилятора для выбранного вами языка, вы можете закодировать, что функция предполагает, что определенный список отсортирован.

Индексированные монады позволяют кодировать некоторые из систем зависимых типов, чтобы более точно управлять побочными эффектами.

5
Sassa NF