大家好,

我今年再次对Haskell很新(在20世纪90年代初使用它之后再在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

(是的,我可以用两个参数将它们抽象成一对,但我还没有完成它。)当然,你可以看到我的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足以使用其中的等效方法。 然而,即使我成功了,状态Monad实现似乎使用丑陋的“ get ”和“ put ”(以及“ runState ”等)并使代码不易读取,而不是更多。

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

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

===============>>#1 票数:9 已采纳

您可以在不使用getput情况下使用State monad。 只需将状态传递函数直接包装在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下的StateT StdGen

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

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 ,使用mapM ,然后打开它。

  ask by Software Engineer translate from so

未解决问题?本站智能推荐: