2014-09-11 29 views
2

假设我想生成一个项目列表,同时跟踪某些状态。例如,生成[1..],同时跟踪目前生成的项目,或生成一个随机数列表,记录RNG状态。生成带状态列表的最佳方式(Haskell)

看来,这样做有两种方式:

1)生成国家单子,[State s a]的列表,然后运行sequence得到State s [a]。然后用runState得到[a]。 2)以某种方式使用Monad变压器。

monad变形金刚方式的一个很好的例子是什么?哪一种比较习惯?每个人的优点和缺点是什么?

+1

monad变换器只是一种类型,它将其他monad作为参数,并将其他monadic效果添加到原始monad中。你永远不需要碰变压器来做你想做的事情。如果你需要跟踪状态,你可以使用'State';使用monad变压器并不意味着你必须或不能使用'State'。已经有一个包裹,它精确地使用'State'来纯粹地包装产生随机数的概念。 – user2407038 2014-09-11 06:59:28

+0

这正是管道和导管的用途。 – 2014-09-11 08:11:38

回答

4

我最近不得不做很多这方面的工作,我发现[State s a]sequence是最好的选择。我想不出用Monad变形金刚这么做的有用/简单的方法。即使列表是Monad,使用sequence意味着我们不必担心Monad内部的Monad。

我不得不使用MaybeT创建一个Maybe Rand,但从来没有列表和状态。尽管我只在哈斯克尔工作了几个月,所以可能有其他人可以用更多的经验来回答他们。

但是 - 并不总是找到使用Monads的方法。有时我发现不使用Monad更容易,而是使用Data.List附带的一些高阶函数。

这里是弘扬与列表值的某些方面,不涉及国家单子(如输入GHCI):

> :t scanl 
scanl :: (a -> b -> a) -> a -> [b] -> [a] 
> scanl (+) 0 [1,2,3,4,5] 
[0,1,3,6,10,15] 

> :t mapAccumL 
mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y]) 
> mapAccumL (\acc x -> (x+acc,x*2)) 0 [1,2,3,4,5] 
(15,[2,4,6,8,10]) 

> :t unfoldr 
unfoldr :: (b -> Maybe (a, b)) -> b -> [a] 
> take 20 . unfoldr (\g -> Just $ randomR (0,10) g) $ mkStdGen 444 
[2,3,8,0,7,5,2,10,10,5,10,2,4,2,8,9,1,1,5,10] 

NB。你必须为scanlmapAccumLunfoldr

导入时Data.List模块使用随机数的工作有时很容易产生我们所需要,而不是创建一个[Rand StdGen Int]列表随机数的列表。例如,这功能,其生成使用applicatives随机数的随机大小的列表:

> :t runRand 
runRand :: RandomGen g => Rand g a -> g -> (a, g) 
> fst . flip runRand (mkStdGen 12345) $ take <$> getRandomR (10,15) <*> getRandomRs (10,20) :: [Int] 
[17,16,16,19,16,17,15,12,10,11,11,10,17,12,13] 
4

你的例子是相当正交StateTState之间的差异。

如果我们使用状态,这样

import System.Random 
import Control.Monad.State 

rs :: State StdGen Int 
rs = do 
    (r,g) <- random `fmap` get 
    put g 
    return (r :: Int) 

main = getStdGen >>= \g -> mapM_ print . flip evalState g . replicateM 4 $ rs 

生成4张随机数,我们跑了State行动四次,在列表中收集到的结果和最后的印刷列表中的每个号码。但是,如果由于某种原因,我们需要在的每个动作中执行IO ,而不是在收集结果之后呢?这是变压器成为相关

import System.Random 
import Control.Monad.State 

rs' :: StateT StdGen IO Int 
rs' = do 
    (r,g) <- random `fmap` get 
    put g 
    liftIO $ print r 
    return (r :: Int) 

main = getStdGen >>= \g -> flip evalStateT g . replicateM_ 4 $ rs' 

注意的是,虽然最终的结果仍然可以访问,我用replicateM_丢弃它。

所以它不是解决这个问题的不同方法的问题,而是你是否需要“更大的锤子”来混合不同monads的动作。