Писатель Монада и Непоследовательность
Я использую 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 вопроса:
- Можно ли определить
unsequence
используя монадические и комонадные операторы, не относящиеся кWriter
? - Есть ли более элегантная реализация
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
; посмотрите этот ответ для обсуждения того, что он делает.