大家好,
今年我对 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 a
types(类型同义词)与上面的完全相似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 种子。我的大部分方法都是这样的(使用WithRNG
or WithGS
),使用链式状态,有时甚至达到r4
or 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
感谢您的任何想法、建议、参考和您的时间!