在haskell中构建一个非确定性的monad转换器

Posted

技术标签:

【中文标题】在haskell中构建一个非确定性的monad转换器【英文标题】:Building a nondeterministic monad transformer in haskell 【发布时间】:2012-12-14 19:10:09 【问题描述】:

我想在 haskell 中构建一个不确定的 monad 转换器,我相信它的行为不同于 ListT 和 http://www.haskell.org/haskellwiki/ListT_done_right 提出的替代 ListT。其中第一个将 monad 与项目列表相关联;第二个将 monad 与单个项目相关联,但具有给定元素中的 monadic 动作影响列表后续槽中的 monadic 元素的属性。目标是构建一个形式的单子变换器

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

这样列表中的每个元素都有自己的 monad 与之关联,并且连续的元素具有独立的 monad。在这篇文章的最后,我稍微演示了这个 monad 应该给出的那种行为。如果您知道如何获得 ListT 的一些变体来提供这种行为,那也会很有帮助。

以下是我的尝试。它不完整,因为 unpack 函数未定义。我该如何定义它?这是定义它的一次不完整的尝试,但是当 monad 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]                                                                                                       

【问题讨论】:

你看过logict吗? 只是一个注释,在你不完整的unpackfirst = do (Cons x _) &lt;- m; x 。您不需要joinreturn 的额外层。 @DanielWagner 根据您的建议,我查看了 logict 并在帖子末尾添加了一个示例,说明了为什么我认为 logict 不能满足我的要求。 @Eyal 很高兴您添加了示例。 StateT 已经做了你想做的事。 【参考方案1】:

我相信你可以使用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]

【讨论】:

我的示例要求incr &gt;&gt; (incr `mplus` incr) 的行为而不是incr `mplus` (incr &gt;&gt; incr)。谜团在于如何将外部incr 的效果“分配”到内部。 @Eyal 试试看。 print (monomorphicExecStateT (incr &gt;&gt; sample1) 0) 打印 [2,2],就像你要求的那样。最初的 incr &gt;&gt; 在您的问题中实际上并不有趣 - 只有 mplus 行为有趣且困难。至于你说你不要求incr `mplus` (incr &gt;&gt; incr),好吧,看看你的示例代码中y的定义。 =) 嗯,我明白你的意思了。也许它就像你建议的那样简单。在我的实际用例中,我的基本 monad 不是 State,而是我自己制作的一些 monad。所以这意味着不是用一些 monad 转换器来转换我的基本 monad,我需要开发一个基本 monad 的 monad 转换器版本并用它来转换列表 monad?【参考方案2】:

我认为这在一般情况下是不可能的(单子转换器应该能够转换任何单子)。您提到的 unpack 选项通常不适用于 monad - 它对应于操作:

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

这是对共单子的操作(单子的数学对偶)。

您可以通过使用 (m (Amb m a)) 并将其映射多次以在每种情况下生成单个 (m a) 来“解包”它,但这需要您提前知道(或而是从 monad 外部)创建了多少选择,如果没有某种形式的提取操作,您将无法知道。

在第二个 ListT 中,列表的尾部依赖于单子动作的原因是因为在某些情况下我们需要执行单子动作以找出产生了多少选择(以及列表的长度)是)。

【讨论】:

谢谢,我想这就是我的预期。你能想到如何实现这个问题的解决方案吗?我应该尝试为我的基本单子定义一个共单子实例,还是你会做一些不同的事情? 我几乎写完了一些我认为尽可能接近的东西——它基本上是一个免费的单子。请在几分钟内与您联系,进一步发表评论。 好吧,它似乎工作,虽然我不建议在没有比我更专业的人看的情况下使用它。 gist.github.com/4288510 某些解释可能比您需要的更详细,我不确定您对 Haskell 的熟悉程度(而且我不是专家)。

以上是关于在haskell中构建一个非确定性的monad转换器的主要内容,如果未能解决你的问题,请参考以下文章

在 Haskell 中使用 Logic Monad

为啥 Haskell 异常只能在 IO monad 中捕获?

Haskell Monad(下)

函数式编程-将Monad(单子)融入Swift

Monads(Haskell)的主要目的[重复]

Haskell学习-monad