假设我想生成一个项目列表,同时跟踪一些状态。例如,生成[1..]
,同时跟踪到目前为止生成的项目,或生成随机数列表,跟踪RNG状态。
似乎有两种方法可以做到这一点:
1)生成State Monads列表[State s a]
,然后运行sequence
以获得State s [a]
。然后使用runState
获取[a]
。
2)以某种方式使用Monad变形金刚。
monad变形金刚方式的一个很好的例子是什么?哪个更惯用?每个人的利弊是什么?
答案 0 :(得分:4)
我最近必须做很多事情,我发现[State s a]
和sequence
是最好的选择。我无法想到使用Monad变形金刚这样做的有用/简单方法。即使list是Monad,使用sequence
也意味着我们不必担心Monad中的Monad。
我必须使用MaybeT
来创建Maybe Rand
,但从不使用列表和状态。虽然我只做了几个月的Haskell,所以可能还有其他人可以回答他们背后的更多经验。
然而 - 并不总是想找到一种方法来使用Monads。有时我发现不使用Monad更容易,而是使用Data.List附带的一些高阶函数。
以下是一些使用不涉及Monad状态(作为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。您必须导入scanl
,mapAccumL
和unfoldr
使用随机数时,有时可以更轻松地生成我们需要的随机数列表,而不是创建[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]
答案 1 :(得分:4)
您的示例与StateT
和State
之间的差异非常正交。
如果我们使用State生成四个随机数,就像这样
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
我们四次运行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_
将其丢弃。
所以问题不在于解决这个问题的不同方法,而在于你是否需要更大的锤子&#34;为了混合不同单子的行为。