Всегда ли композиция произвольной монады с проходимым монадой?
Если у меня есть две монады m
а также n
, а также n
проходимо, обязательно ли у меня составной m
-над-n
монада?
Более формально вот что я имею в виду:
import Control.Monad
import Data.Functor.Compose
prebind :: (Monad m, Monad n) =>
m (n a) -> (a -> m (n b)) -> m (n (m (n b)))
mnx `prebind` f = do nx <- mnx
return $ do x <- nx
return $ f x
instance (Monad m, Monad n, Traversable n) => Monad (Compose m n) where
return = Compose . return . return
Compose mnmnx >>= f = Compose $ do nmnx <- mnmnx `prebind` (getCompose . f)
nnx <- sequence nmnx
return $ join nnx
Естественно, эта проверка типов, и я считаю, работает для нескольких случаев, которые я проверял (Reader over List, State over List) - например, составленная "монада" удовлетворяет законам монады - но я не уверен, что это это общий рецепт для наложения любой монады на проходимую.
2 ответа
Нет, это не всегда монада. Вам нужны дополнительные условия совместимости, относящиеся к монадным операциям двух монад и закону распределения. sequence :: n (m a) -> m (n a)
, как описано, например, в Википедии.
Ваш предыдущий вопрос приводит пример, в котором условия совместимости не выполняются, а именно
S = m = []
, с единицей X -> SX, отправляющей x в [x];
T = n = (->) Bool
или, что то же самое, TX = X × X, причем единица X -> TX отправляет x в (x,x).
Правая нижняя диаграмма на странице Википедии не коммутирует, так как композиция S -> TS -> ST отправляет xs :: [a]
в (xs,xs)
а затем к декартову произведению всех пар, взятых из xs
; в то время как правая карта S -> ST отправляет xs
на "диагональ", состоящую только из пар (x,x)
за x
в xs
, Это та же проблема, из-за которой предложенная монада не удовлетворяла одному из законов юнитов
Несколько дополнительных замечаний, чтобы сделать связь между общим ответом Рейда Бартона и вашим конкретным вопросом более явной.
В этом случае это действительно окупается Monad
пример с точки зрения join
:
join' :: m (n (m (n b))) -> m (n b)
join' = fmap join . join . fmap sequence
Повторно вводя compose
/getCompose
в соответствующих местах и используя m >>= f = join (fmap f m)
, вы можете проверить, что это действительно эквивалентно вашему определению (обратите внимание, что ваш prebind
составляет fmap f
в этом уравнении).
Это определение позволяет проверить законы с помощью диаграмм1. Вот один для join . return = id
т.е. (fmap join . join . fmap sequence) . (return . return) = id
:
3210 MT ID MT ID MT ID MT ----> ----> ----> rT2 | | rT1 | | rT1 | | id rM3 V V rM3 V V V V ----> ----> ----> MTMT sM2 MMTT jM2 MTT jT0 MT
Общий прямоугольник - это закон монады:
M id M ----> rM1 | | Я бы ВВ ----> MM jM0 M
Игнорируя части, которые обязательно одинаковы в обоих направлениях через квадраты, мы видим, что два самых правых квадрата равносильны одному и тому же закону. (Конечно, немного глупо называть эти "квадраты" и "прямоугольники", учитывая все id
у них есть стороны, но это лучше подходит моим ограниченным художественным навыкам ASCII.) Первый квадрат, тем не менее, составляет sequence . return = fmap return
, которая является правой нижней диаграммой на странице Википедии, упоминает Рейд Бартон...
M id M ----> rT1 | | RT0 ВВ ----> ТМ СМ1 МТ
... и это не дано, что верно, как показывает ответ Рейда Бартона.
Если мы применим ту же стратегию к join . fmap return = id
закон, правая верхняя диаграмма, sequence . fmap return = return
, показывает, что, однако, это не проблема сама по себе, поскольку это просто (непосредственное следствие) закон идентичности Traversable
, Наконец, сделать то же самое с join . fmap join = join . join
закон делает две другие диаграммы - sequence . fmap join = join . fmap sequence . sequence
а также sequence . join = fmap join . sequence . fmap sequence
- весна вперед.
Примечания:
- Легенда для стенографии:
r
являетсяreturn
,s
являетсяsequence
а такжеj
являетсяjoin
, Буквы и цифры в верхнем регистре после сокращений функций устраняют неоднозначность для задействованной монады, и позиция ее введенного или измененного слоя заканчивается на - в случаеs
, что относится к тому, что изначально является внутренним слоем, так как в этом случае мы знаем, что внешний слой всегдаT
, Слои нумеруются снизу вверх, начиная с нуля. Композиция указывается путем написания сокращенной записи для второй функции под первой.