大家好,
今年我对 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 a和WithRNG 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
感谢您的任何想法、建议、参考和您的时间!