Создание недетерминированного преобразователя монад в Haskell

Я хотел бы построить недетерминированный преобразователь монады в Haskell, который, как мне кажется, ведет себя иначе, чем ListT и альтернативный ListT, предложенный на http://www.haskell.org/haskellwiki/ListT_done_right. Первый из них связывает монаду со списком элементов; второй связывает монаду с отдельными элементами, но обладает тем свойством, что монадические действия в данном элементе влияют на монадические элементы в последующих слотах списка. Цель состоит в том, чтобы построить монадный преобразователь вида

data Amb m a = Cons (m a) (Amb m a) | Empty

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

Ниже моя попытка. Он неполный, потому что функция unpack не определена. Как я могу определить это? Вот одна неполная попытка его определения, но она не учитывает случай, когда монада m содержит список Empty Amb:

unpack :: (Monad m) => m (Amb m a) -> Amb m a                                                                                                                 
unpack m = let first = join $ do (Cons x ys) <- m                                                                                                             
                                 return x                                                                                                                     
               rest =  do (Cons x ys) <- m                                                                                                                    
                          return ys                                                                                                                           
           in Cons first (unpack rest)   

Полный (неполный) код:

import Prelude hiding  (map, concat)                                                                                                                          
import Control.Monad                                                                                                                                          
import Control.Monad.Trans       

data Amb m a = Cons (m a) (Amb m a) | Empty                                                                                                                   

infixr 4 <:>                                                                                                                                                  
(<:>) = Cons                                                                                                                                                  

map :: Monad m => (a -> b) -> Amb m a -> Amb m b                                                                                                              
map f (Cons m xs) = Cons y (map f xs)                                                                                                                         
    where y = do a <- m                                                                                                                                       
                 return $ f a                                                                                                                                 
map f Empty = Empty                                                                                                                                           

unpack :: m (Amb m a) -> Amb m a                                                                                                                              
unpack m = undefined                                                                                                                                          


concat :: (Monad m) => Amb m (Amb m a) -> Amb m a                                                                                                             
concat (Cons m xs)  = (unpack m) `mplus` (concat xs)                                                                                                          
concat  Empty = Empty                                                                                                                                         

instance Monad m => Monad (Amb m) where                                                                                                                       
    return x = Cons (return x) Empty                                                                                                                          
    xs >>= f = let yss = map f xs                                                                                                                             
               in concat yss                                                                                                                                  

instance Monad m => MonadPlus (Amb m) where                                                                                                                   
    mzero = Empty                                                                                                                                             
    (Cons m xs) `mplus` ys = Cons m (xs `mplus` ys)                                                                                                           
    Empty `mplus` ys = ys                                                                                                                                     

instance MonadTrans Amb where                                                                                                                                 
    lift m = Cons m Empty        

Примеры желаемого поведения

Здесь базовая монада State Int

instance Show a => Show (Amb (State Int) a) where                                                                                                             
    show m = (show .  toList) m                                                                                                                               


toList :: Amb (State Int) a -> [a]                                                                                                                            
toList Empty = []                                                                                                                                             
toList (n `Cons` xs) = (runState n 0 : toList xs)                                                                                                             


x = (list $ incr) >> (incr <:> incr <:> Empty)                                                                                                                
y = (list $ incr) >> (incr <:> (incr >> incr) <:> Empty)                                                                                                      

main = do                                                                                                                                                     
  putStr $ show x -- | should be [2, 2]                                                                                                                       
  putStr $ show y -- | should be [2, 3]   

Спасибо.

Обновление: пример того, почему LogicT не делает то, что мне нужно.

Вот что делает LogicT на простом примере выше:

import Control.Monad                                                                                                                                          
import Control.Monad.Logic                                                                                                                                    
import Control.Monad.State                                                                                                                                    

type LogicState = LogicT (State Int)                                                                                                                          


incr :: State Int Int                                                                                                                                         
incr = do i <- get                                                                                                                                            
          put (i + 1)                                                                                                                                         
          i' <- get                                                                                                                                           
          return i'                                                                                                                                           

incr' = lift incr                                                                                                                                             
y =  incr' >> (incr' `mplus` incr')                                                                                                                           

main = do                                                                                                                                                     
  putStrLn $ show (fst $ runState (observeAllT y) 0)   -- | returns [2,3], not [2,2]                                                                                                       

person Eyal    schedule 14.12.2012    source источник
comment
Вы ознакомились с логикой?   -  person Daniel Wagner    schedule 15.12.2012
comment
Просто примечание, в ваших неполных unpack, first = do { (Cons x _) <- m; x }. Вам не нужен дополнительный слой join и return.   -  person huon    schedule 15.12.2012
comment
@DanielWagner По вашему предложению я посмотрел на логикт и добавил в конце поста пример, показывающий, почему я думаю, что логикт не делает то, что я хочу.   -  person Eyal    schedule 17.12.2012
comment
@Eyal Я рад, что вы добавили примеры. StateT уже делает то, что вы хотите.   -  person Daniel Wagner    schedule 17.12.2012


Ответы (2)


Я считаю, что вы можете просто использовать StateT. Например:

import Control.Monad.State

incr = modify (+1)
sample1 = incr `mplus` incr
sample2 = incr `mplus` (incr >> incr)

monomorphicExecStateT :: StateT Int [] a -> Int -> [Int]
monomorphicExecStateT = execStateT

main = do
    print (monomorphicExecStateT sample1 0) -- [1, 1]
    print (monomorphicExecStateT sample2 0) -- [1, 2]
person Daniel Wagner    schedule 17.12.2012
comment
В моем примере требуется поведение incr >> (incr `mplus` incr), а не incr `mplus` (incr >> incr). Загадка в том, как распределить влияние внешних incr на внутренние. - person Eyal; 17.12.2012
comment
@Eyal Просто попробуйте и посмотрите. print (monomorphicExecStateT (incr >> sample1) 0) печатает [2,2], как вы и просили. Начальный incr >> на самом деле не интересен в вашем вопросе - только поведение mplus интересно и сложно. Что касается того, что вы говорите, что не запрашиваете incr `mplus` (incr >> incr), просто посмотрите на определение y в вашем примере кода. знак равно - person Daniel Wagner; 17.12.2012
comment
Хм, я понимаю вашу точку зрения. Может быть, это так просто, как вы предлагаете. В моем реальном случае моей базовой монадой является не состояние, а некоторая монада моего собственного создания. Значит, это означает, что вместо того, чтобы преобразовывать мою базовую монаду с помощью некоторого преобразователя монады, мне нужно разработать версию преобразователя монады моей базовой монады и преобразовать монаду списка с ее помощью? - person Eyal; 18.12.2012

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

extract :: (Comonad w) => w a -> a

Это операция над комонадой (математическим двойником монады).

Есть вещи, которые вы можете сделать, чтобы «распаковать» его, взяв (m (Amb ma)) и сопоставив его несколько раз, чтобы получить один (ma) в каждом случае, но это требует, чтобы вы знали заранее (или, скорее, из вне монады), сколько вариантов выбора создается, о чем вы не можете узнать без какой-либо операции извлечения.

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

person Community    schedule 14.12.2012
comment
Спасибо, я думаю, это то, что я ожидал. Можете ли вы придумать, как реализовать решение этой проблемы? Должен ли я попытаться определить экземпляр комонады для моей базовой монады, или вы сделаете что-то другое? - person Eyal; 14.12.2012
comment
Я почти закончил писать что-то, что, по моему мнению, максимально похоже на то, что вы можете легко понять - это, по сути, свободная монада. Будьте с вами через пару минут дальнейшим комментарием. - person ; 15.12.2012
comment
Хорошо, похоже, это работает, хотя я бы не советовал использовать его без того, чтобы кто-то более опытный, чем я, посмотрел. gist.github.com/4288510 Некоторые пояснения могут содержать немного больше деталей, чем вам нужно, я не был уверен, насколько вы точно знакомы с Haskell (и я не эксперт). - person ; 15.12.2012