Подтвердить что ты не робот

Существует ли Codensity MonadPlus, который асимптотически оптимизирует последовательность операций MonadPlus?

Недавно был вопрос о связи между DList[] по сравнению с CodensityFree.

Это заставило меня подумать, есть ли такая вещь для MonadPlus. Монада Codensity улучшает асимптотические характеристики только для монадических операций, а не для mplus.

Кроме того, когда раньше было Control.MonadPlus.Free, оно было удалено в пользу FreeT f []. И поскольку нет явного бесплатного MonadPlus, я не уверен, как можно было бы выразить соответствующий вариант improve. Возможно, что-то вроде

improvePlus :: Functor f => (forall m. (MonadFree f m, MonadPlus m) => m a) -> FreeT f [] a

?


Обновление: Я попытался создать такую ​​монаду, используя обратную трассировку LogicT monad, которая кажется быть определена аналогично Codensity:

newtype LogicT r m a = LogicT { unLogicT :: forall r. (a -> m r -> m r) -> m r -> m r }

и подходит для вычислений обратного слежения, то есть MonadPlus.

Затем я определил lowerLogic, аналогичный lowerCodensity как followd:

{-# LANGUAGE RankNTypes, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses,
             UndecidableInstances, DeriveFunctor #-}
import Control.Monad
import Control.Monad.Trans.Free
import Control.Monad.Logic

lowerLogic :: (MonadPlus m) => LogicT m a -> m a
lowerLogic k = runLogicT k (\x k -> mplus (return x) k) mzero

Затем, добавив соответствующий экземпляр MonadFree

instance (Functor f, MonadFree f m) => MonadFree f (LogicT m) where
    wrap t = LogicT (\h z -> wrap (fmap (\p -> runLogicT p h z) t))

можно определить

improvePlus :: (Functor f, MonadPlus mr)
            => (forall m. (MonadFree f m, MonadPlus m) => m a)
            -> FreeT f mr a
improvePlus k = lowerLogic k

Однако с ним что-то не так, как кажется из моих первоначальных экспериментов, что для некоторых примеров k отличается от improvePlus k. Я не уверен, если это фундаментальное ограничение LogicT и требуется другая, более сложная монада или просто если я неправильно определил lowerLogic (или что-то еще).

4b9b3361

Ответ 1

Следующее все основано на моем (неправильном) понимании этой очень интересной статьи, опубликованной Мэтью Пикерингом в его комментарии: " От моноидов до почти полуколец: сущность MonadPlus и Alternative" (Э. Ривас, М. Яскелиофф, Т. Шрайверс), Все результаты их; все ошибки мои.

От бесплатных моноидов до DList

Чтобы построить интуицию, сначала рассмотрим свободный моноид [] над категорией типов Haskell Hask. Одна проблема с [] заключается в том, что если у вас есть

(xs 'mappend' ys) 'mappend' zs = (xs ++ ys) ++ zs

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

Решение состоит в том, чтобы использовать CPS в форме списков различий:

newtype DList a = DL { unDL :: [a] -> [a] }

В статье рассматривается общая форма этого (так называемое представление Кэли), где мы не привязаны к свободному моноиду:

newtype Cayley m = Cayley{ unCayley :: Endo m }

с преобразованиями

toCayley :: (Monoid m) => m -> Cayley m
toCayley m = Cayley $ Endo $ \m' -> m 'mappend' m'

fromCayley :: (Monoid m) => Cayley m -> m
fromCayley (Cayley k) = appEndo k mempty

Два направления обобщения

Мы можем обобщить вышеуказанную конструкцию двумя способами: во-первых, рассматривая моноиды не над Hask, а над эндофункторами Hask; т.е. монады; и во-вторых, путем обогащения алгебраической структуры до почти полуколец.

Free монады в Codensity

Для любого функтора Хаскелла (эндо) f мы можем построить свободную монаду Free f, и она будет иметь аналогичную проблему производительности с лево-вложенными связями, с аналогичным решением с использованием Codensity представления Кэли.

Почти полукольца вместо моноидов

Здесь документ перестает рассматривать концепции, хорошо известные работающему программисту на Haskell, и начинает стремиться к своей цели. Почти полукольцо похоже на кольцо, за исключением более простого, так как сложение и умножение просто должны быть моноидами. Связь между этими двумя операциями - это то, что вы ожидаете:

zero |*| a = zero
(a |+| b) |*| c = (a |*| c) |+| (b |*| c)

где (zero, |+|) и (one, |*|) - два моноида над некоторой общей базой:

class NearSemiring a where
    zero :: a
    (|+|) :: a -> a -> a
    one :: a
    (|*|) :: a -> a -> a

Свободное полукольцо (над Hask) оказывается следующим типом Forest:

newtype Forest a = Forest [Tree a]
data Tree a = Leaf | Node a (Forest a)

instance NearSemiring (Forest a) where
    zero = Forest []
    one = Forest [Leaf]
    (Forest xs) |+| (Forest ys) = Forest (xs ++ ys)
    (Forest xs) |*| (Forest ys) = Forest (concatMap g xs)
      where
        g Leaf = ys
        g (Node a n) = [Node a (n |*| (Forest ys))]

(хорошо, что у нас нет коммутативности или инверсий, они делают свободные представления далеко не тривиальными...)

Затем статья дважды применяет представление Кэли к двум моноидальным структурам.

Однако, если мы сделаем это наивно, мы не получим хорошего представления: мы хотим представить почти полукольцо, и поэтому должна учитываться вся структура почти полукольца, а не только одна выбранная моноидная структура. [...] [W] получаем полукольцо эндоморфизмов над эндоморфизмами DC(N):

newtype DC n = DC{ unDC :: Endo (Endo n) }

instance (Monoid n) => NearSemiring (DC n) where
    f |*| g = DC $ unDC f 'mappend' unDC g
    one = DC mempty
    f |+| g = DC $ Endo $ \h -> appEndo (unDC f) h 'mappend' h
    zero = DC $ Endo $ const mempty

(Я немного изменил реализацию здесь из статьи, чтобы подчеркнуть, что мы используем структуру Endo дважды). Когда мы обобщим это, два слоя не будут одинаковыми. Затем в статье говорится:

Обратите внимание, что rep не является почти полукольцом гомоморфизма из N в DC(N) поскольку он не сохраняет единицу [...] Тем не менее, [...] семантика вычисления над почти полукольцом будет сохранена, если мы поднимаем значения до представления, выполняем там вычисления почти полукольца, а затем возвращаемся к исходному почти полукольцу.

MonadPlus почти полукольцо

Затем в статье переформулируется MonadPlus MonadPlus так, чтобы он соответствовал правилам почти полуколец: (mzero, mplus) был моноидальным:

m 'mplus' mzero = m
mzero 'mplus' m = m
m1 'mplus' (m2 'mplus' m3) = (m1 'mplus' m2) 'mplus' m3

и он взаимодействует с монадой-моноидом, как и ожидалось:

join mzero = mzero
join (m1 'mplus' m2) = join m1 'mplus' join m2

Или, используя связки:

mzero >>= _ = mzero
(m1 'mplus' m2) >>= k = (m1 >>= k) 'mplus' (m2 >>= k)

Однако это не правила существующего MonadPlus типов MonadPlus из base, которые перечислены как:

mzero >>= _  =  mzero
_ >> mzero   =  mzero

Документ призывает MonadPlus экземпляры, которые удовлетворяют ближние-полукольцо -like законы "недетерминизма монады", и цитирует Maybe, в качестве примера, который является MonadPlus но не недетерминизм монада, так как установка m1 = Just Nothing и m2 = Just (Just False) контрпример к join (m1 'mplus' m2) = join m1 'mplus' join m2.

Свободное и Кэли представление недетерминированных монад

Собирая все воедино, с одной стороны, у нас есть свободная недетерминистская монада Forest -like:

newtype FreeP f x = FreeP { unFreeP :: [FFreeP f x] }
data FFreeP f x = PureP x | ConP (f (FreeP f x))

instance (Functor f) => Functor (FreeP f) where
    fmap f x = x >>= return . f

instance (Functor f) => Monad (FreeP f) where
    return x = FreeP $ return $ PureP x
    (FreeP xs) >>= f = FreeP (xs >>= g)
      where
        g (PureP x) = unFreeP (f x)
        g (ConP x) = return $ ConP (fmap (>>= f) x)

instance (Functor f) => MonadPlus (FreeP f) where
    mzero = FreeP mzero
    FreeP xs 'mplus' FreeP ys = FreeP (xs 'mplus' ys)

и с другой стороны, двойное представление Кэли двух моноидальных слоев:

newtype (:^=>) f g x = Ran{ unRan :: forall y. (x -> f y) -> g y }
newtype (:*=>) f g x = Exp{ unExp :: forall y. (x -> y) -> (f y -> g y) }

instance Functor (g :^=> h) where
    fmap f m = Ran $ \k -> unRan m (k . f)

instance Functor (f :*=> g) where
    fmap f m = Exp $ \k -> unExp m (k . f)

newtype DCM f x = DCM {unDCM :: ((f :*=> f) :^=> (f :*=> f)) x}

instance Monad (DCM f) where
    return x = DCM $ Ran ($x)
    DCM (Ran m) >>= f = DCM $ Ran $ \g -> m $ \a -> unRan (unDCM (f a)) g

instance MonadPlus (DCM f) where
    mzero = DCM $ Ran $ \k -> Exp (const id)
    mplus m n = DCM $ Ran $ \sk -> Exp $ \f fk -> unExp (a sk) f (unExp (b sk) f fk)
      where
        DCM (Ran a) = m
        DCM (Ran b) = n

caylize :: (Monad m) => m a -> DCM m a
caylize x = DCM $ Ran $ \g -> Exp $ \h m -> x >>= \a -> unExp (g a) h m

-- I wish I called it DMC earlier...
runDCM :: (MonadPlus m) => DCM m a -> m a
runDCM m = unExp (f $ \x -> Exp $ \h m -> return (h x) 'mplus' m) id mzero
  where
    DCM (Ran f) = m

В статье приведен следующий пример вычислений, выполняемых в монаде недетерминизма, которая плохо работает для FreeP:

anyOf :: (MonadPlus m) => [a] -> m a
anyOf [] = mzero
anyOf (x:xs) = anyOf xs 'mplus' return x

Действительно, пока

length $ unFreeP (anyOf [1..100000] :: FreeP Identity Int)

берет возраст, версия, преобразованная Кэли

length $ unFreeP (runDCM $ anyOf [1..100000] :: FreeP Identity Int)

возвращается мгновенно.