Писатель Монада и Непоследовательность

Я использую Writer Монада для отслеживания флага ошибки ("столкновения") на произвольных значениях (таких как Int). Как только флаг установлен, он становится "липким" и присоединяется ко всем значениям, полученным в результате любой операции с отмеченным.

Иногда флаг столкновения связан с отдельными значениями, иногда я хотел бы связать с составными структурами, такими как списки. Конечно, как только флаг столкновения установлен для всего списка, также имеет смысл предположить, что он установлен для отдельного элемента. Так что для писателя монада m Мне нужны две следующие операции:

sequence :: [m a] -> m [a]
unsequence :: m [a] -> [m a]

Первый определяется в Prelude, а второй должен быть определен. Вот хорошее обсуждение того, как это можно определить с помощью комонад. Нативная реализация comonad не сохраняет состояние. Вот пример:

{-# LANGUAGE FlexibleInstances #-}

module Foo where    

import Control.Monad.Writer
import Control.Comonad

unsequence :: (Comonad w, Monad m) => w [a] -> [m a]
unsequence = map return . extract

instance Monoid Bool where
    mempty = False
    mappend = (||)

type CM = Writer Bool
type CInt = CM Int

instance (Monoid w) => Comonad (Writer w) where
    extract x = fst $ runWriter x
    extend f wa = do { tell $ execWriter wa ; return (f wa)}

mkCollision :: t -> Writer Bool t
mkCollision x = do (tell True) ; return x

unsequence1 :: CM [Int] -> [CInt]
unsequence1 a = let (l,f) = runWriter a in
              map (\x -> do { tell f ; return x}) l

el = mkCollision [1,2,3]

ex2:: [CInt]      
ex2 = unsequence el
ex1 = unsequence1 el

ex1 выдает правильное значение, а ex2 на выходе неправильно сохраняется флаг столкновения:

*Foo> ex1
[WriterT (Identity (1,True)),WriterT (Identity (2,True)),WriterT (Identity (3,True))]
*Foo> ex2
[WriterT (Identity (1,False)),WriterT (Identity (2,False)),WriterT (Identity (3,False))]
*Foo> 

В связи с этим у меня есть 2 вопроса:

  1. Можно ли определить unsequence используя монадические и комонадные операторы, не относящиеся к Writer?
  2. Есть ли более элегантная реализация extend функция выше, возможно, похожа на эту?

Спасибо!

1 ответ

Решение

ex1 выдает правильное значение, а ex2 на выходе неправильно сохраняется флаг столкновения:

unsequence (и, как следствие, ex2) не работает, потому что выбрасывает Writer журнал.

unsequence :: (Comonad w, Monad m) => w [a] -> [m a]
unsequence = map return . extract

extract для тебя Comonad instance дает результат вычисления, отбрасывая журнал. return добавляет mempty войти в голые результаты. При этом флаги очищаются в ex2,

unsequence1 с другой стороны, делает то, что вы хотите. Это явно не имеет ничего общего с Comonad (ваше определение не использует его методы); скорее, unsequence1 работает потому что... это на самом деле sequence! Под капотом, Writer это просто пара результатов и (моноидальный) лог. Если у вас есть второй взгляд на unsequence1 Имея это в виду, вы заметите, что (по модулю нерелевантных деталей) это по сути то же самое, что и sequence для пар - аннотирует значения в другом функторе с помощью журнала:

GHCi> sequence (3, [1..10])
[(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),(3,7),(3,8),(3,9),(3,10)]

По факту, Writer уже есть Traversable экземпляр просто так, так что вам даже не нужно его определять:

GHCi> import Control.Monad.Writer
GHCi> import Data.Monoid -- 'Any' is your 'Bool' monoid.
GHCi> el = tell (Any True) >> return [1,2,3] :: Writer Any [Int]
GHCi> sequence el
[WriterT (Identity (1,Any {getAny = True})),WriterT (Identity (2,Any {getAny = True})),WriterT (Identity (3,Any {getAny = True}))]

Стоит отметить, что sequence не является по существу монадической операцией - Monad ограничение в sequence излишне ограничительный. Реальная сделка sequenceA, который требует только Applicative ограничение на внутренний функтор. (Если внешний Functor - то есть тот, с Traversable экземпляр - это как Writer w в том, что он всегда "содержит" ровно одно значение, тогда вам даже не нужно Applicative, но это уже другая история.)

Можно ли определить "непоследовательность", используя монадические и комонадные операторы, не специфичные для "Writer"

Как уже говорилось выше, вы на самом деле не хотите unsequence, Есть класс под названием Distributive это обеспечивает unsequence (под именем distribute); однако, между вещами с Distributive случаи и вещи с Traversable те, и в любом случае это по существу не включает в себя комад.

Существует ли более элегантная реализация функции расширения, описанная выше, возможно, похожая на эту?

Ваш Comonad экземпляр в порядке (он действительно следует законам комонад), за исключением того, что Monoid ограничение в этом. Парная комонада обычно известна как Env; посмотрите этот ответ для обсуждения того, что он делает.

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