gpt4 book ai didi

haskell - 创建类似于具有链式状态的 IO Monad 的 monad

转载 作者:行者123 更新时间:2023-12-02 15:35:24 27 4
gpt4 key购买 nike

大家好,

今年我再次对 Haskell 很陌生(在 20 世纪 90 年代初使用它,然后在 00 年代初再次使用它)。我正在尝试编写一些使用几乎直接类似于 example IO monad shown on the Haskell Wiki 的模式的代码。 :

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

(是的,我知道这不是 IO 的 GHC 实现,而只是理解它的一种工具。)原因是,在我的应用程序(游戏)中,我现在有两种模式通过两种不同的替换来执行此操作此处的真实世界。在一种情况下,它是游戏的状态,在另一种情况下,它只是一个 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

(是的,我可以将它们抽象为带有两个参数的一对,但我还没有抽出时间。)当然,您可以看到我的 WithGS aWithRNG a 类型(类型同义词)与上面的 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 种子。我的大部分方法都是这样的(使用 WithRNGWithGS),使用链式状态,有时甚至达到 r4r6(或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”或使用参数。使用 newtype 似乎也会添加无用的内容丑陋的语法。)我还没有能够很好地弄清楚 State Monad 来使用它来创建一个等效的方法。然而,即使我成功了,State Monad 实现似乎也会使用丑陋的“get”和“put”(以及“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

感谢您的任何想法、建议、引用和您的时间!

最佳答案

您可以使用 State monad,而无需使用 getput。只需将状态传递函数直接包装在 State newtype 中即可:

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 构造函数采用状态传递函数并创建一个合适的值以在 State monad 中使用。然后,当您使用完 monad 后,您可以使用 runState 将 monad 转换回等效的状态传递函数:

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

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

这实际上就是 RandT 的工作原理,将所有生成器传递的随机函数包装在一个状态 monad 中,并且 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,然后解开它。

关于haskell - 创建类似于具有链式状态的 IO Monad 的 monad,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16026955/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com