【问题标题】:Best way to generate a list with state (Haskell)生成带状态列表的最佳方法(Haskell)
【发布时间】:2014-09-11 06:29:41
【问题描述】:

假设我想生成一个项目列表,同时跟踪某些状态。例如,生成[1..],同时跟踪到目前为止生成的项目,或者生成随机数列表,跟踪RNG状态。

似乎有两种方法可以做到这一点:

1) 生成状态单子列表[State s a],然后运行sequence 得到State s [a]。然后使用runState 获取[a]

2) 以某种方式使用 Monad 转换器。

monad 转换器方式的一个很好的例子是什么?哪个更惯用?各有什么优缺点?

【问题讨论】:

  • monad 转换器只是一种类型,它接受一些其他 monad 作为参数,并在原始 monad 中添加一些其他 monadic 效果。你永远不需要触摸变压器来做你想做的事。如果您需要跟踪状态,请使用State;使用 monad 转换器并不意味着您必须或不能使用 State。已经有一个包正好使用State来封装纯粹生成随机数的概念。
  • 这正是管道和导管的用途。

标签: haskell monads monad-transformers state-monad


【解决方案1】:

我最近不得不做很多这样的事情,我发现[State s a]sequence 是最好的选择。我想不出用 Monad Transformers 来做这件事的有用/简单的方法。即使 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]

注意。您必须为 scanlmapAccumLunfoldr 导入 Data.List

使用随机数时,有时生成我们需要的随机数列表比创建[Rand StdGen Int] 列表更容易。例如这个函数使用应用程序生成一个随机大小的随机数列表:

> :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]

【讨论】:

    【解决方案2】:

    您的示例与StateTState 之间的区别完全正交。

    如果我们要使用 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 操作,将结果收集到一个列表中,最后打印出列表中的每个数字。但是,如果出于某种原因,我们需要在每个操作inside 中而不是在收集结果之后进行 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_ 将其丢弃。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-24
      • 2020-07-17
      • 1970-01-01
      • 2021-03-16
      • 1970-01-01
      • 2011-02-01
      • 2010-10-12
      相关资源
      最近更新 更多