6

大家好,

今年我对 Haskell 还是很陌生(在 1990 年代初使用它之后,又在 00 年代初再次使用它)。我正在尝试编写一些代码,它使用的模式几乎直接类似于Haskell Wiki 上显示的示例 IO monad

type IO a  =  RealWorld -> (a, RealWorld)

(是的,我知道这不是 IO 的 GHC 实现,而只是理解它的一种工具。)原因是,在我的应用程序(游戏)中,我现在有两种模式来执行此操作,其中有两种不同的RealWorld替换. 一方面,它是游戏的状态,另一方面,它只是一个StdGen随机数种子。我当然现在有两对这样的类型:

-- | Easily return a specified value as well as the new random number generator
type ReturnRNG a = (a, StdGen)

-- | Take an RNG and return it and another value.
-- (This is basically like the IO type but with the StdGen instead of RealWorld.)
type WithRNG a = StdGen -> ReturnRNG a

-- | Easily return a specified value as well as the new GameState
type ReturnGS a = (a, GameState)

-- | Use a GameState and return a value with the updated GameState.
-- (This is like IO.)
type WithGS a = GameState -> ReturnGS a

(是的,我可以用两个参数将它们抽象成一对,但我还没有解决。)当然,你可以看到 myWithGS aWithRNG atypes(类型同义词)与上面的完全相似IO a

因此,这是我现在拥有的实际工作代码的简单示例:

-- | Returns a random position for the given size.
randomPos :: (Int, Int)          -- ^ The size
          -> WithRNG (Int, Int)  -- ^ The result (0 up to 1 less than the size) and new RNG seed
randomPos (w, h) r0 = ((x, y), r2)
  where
    (x, r1) = randomR (0, w - 1) r0
    (y, r2) = randomR (0, h - 1) r1

这会在指定范围内创建一个随机对,并返回最终的 RNG 种子。我的大部分方法都是这样的(使用WithRNGor WithGS),使用链式状态,有时甚至达到r4or r6(或gs4等)。我宁愿写这个例子看起来像这样......

-- (Not working example)
randomPosSt (w, h) = do
    x <- randomR (0, w - 1)
    y <- randomR (0, h - 1)
    return (x, y)

...但具有完全相同的方法签名和语义。这似乎应该可以按照上述给出这个例子的教程进行:

(>>=) :: IO a -> (a -> IO b) -> IO b
(action1 >>= action2) world0 =
   let (a, world1) = action1 world0
       (b, world2) = action2 a world1
   in (b, world2)

如您所见,这几乎正是我在上面所做的(一旦您将“ let”替换为“ where”符号)。

但是,我不能从类型同义词中创建 Monad。(我已经尝试过 TypeSynonymInstances 但它似乎不适用于“ instance Monad WithRNG where”或使用参数。使用 anewtype似乎也会添加无用的丑陋语法。)我无法很好地弄清楚 State Monad使用它做一个等效的方法。然而,即使我成功了,State Monad 实现似乎也使用了丑陋的“ get”和“ put”s(以及“ runState”s 等),并且使代码可读性降低,而不是更多。

-- THIS DOES NOT WORK
-- | Make a State Monad with random number generator - like WithRNG above
type RandomState = State StdGen

-- | Returns a random position for the given size.
randomPosSt :: (Int, Int)                  -- ^ The size
            -> RandomState (Int, Int)  -- ^ The result (0 up to 1 less than the size) and new RNG seed

经过这一切,我得出的结论是,我要么做错了什么,误解了某些事情,要么就是不能做我想做的事。我正要说“好吧,你真的不需要弄清楚如何修改你的代码来使状态被自动处理,因为它工作得很好”然后放弃了,然后我想我会在这里问(我的处女作潜伏)。我更喜欢更优雅的解决方案。

我还想一个更优雅的解决方案会给我这个我“免费”使用的功能:

-- | Maps the specified method, which must take a RNG as the last parameter,
-- over all the elements of the list, propagating the RNG and returning it.
-- TODO: Implement this without using recursion? Using a fold?
mapRandom :: (a -> WithRNG b) -- ^ The function to map (that takes a RNG)
          -> [a] -- ^ The input list
          -> WithRNG [b] -- ^ The RNG to return
mapRandom func [] r0 = ([], r0)
mapRandom func (x:xs) r0 = (mapped : rest, r2)
  where
    (mapped, r1) = func x r0
    (rest, r2)   = mapRandom func xs r1

感谢您的任何想法、建议、参考和您的时间!

4

1 回答 1

9

您可以在不使用or的State情况下使用 monad 。只需将您的状态传递函数直接包装在新类型中:getputState

import System.Random

newtype State s a = State { runState :: s -> (a, s) }

instance Monad (State s) where
    return a = State (\s -> (a, s))

    m >>= f = State (\s0 ->
        let (a, s1) = runState m s0
        in  runState (f a) s1 )

randomPos :: (Int, Int) -> State StdGen (Int, Int)
randomPos (w, h) = do
    x <- State $ randomR (0, w - 1)
    y <- State $ randomR (0, h - 1)
    return (x, y)

诀窍是观察State构造函数的类型:

State :: (s -> (a, s)) -> State s a

.. 并且randomR (lo, hi)有正确的类型可以直接包装在State

randomR (1, 6)          :: StdGen -> (Int, StdGen)
StateT $ randomR (1, 6) :: State StdGen Int

构造State函数采用状态传递函数并创建一个合适的值以在Statemonad 中使用。然后,当您使用完 monad 后,您可以使用以下命令将 monad 转换回等效的状态传递函数runState

runState :: State s a -> (s -> (a, s))

runState (randomPos (5, 6)) :: StdGen -> ((Int, Int), StdGen)

这实际上是如何RandT工作的,通过将所有通过生成器的随机函数包装在一个状态单子中,并且RandT相当于StateT StdGen在引擎盖下。

另外,就像你说的,一元公式会免费为你提供映射版本:

mapRandom
    :: ( a  -> (StdGen -> ( b , StdGen)))
    -> ([a] -> (StdGen -> ([b], StdGen)))
mapRandom f xs = runState $ mapM (State . f) xs

这是因为mapM(当专门用于State)的类型是:

mapM :: (a -> State s b) -> [a] -> State s [b]

所以上面的mapRandom函数所做的就是将输入包装成一个State新类型,使用mapM,然后解包。

于 2013-04-16T04:51:41.000 回答