6

みんなこんにちは、

私は今年も Haskell にかなり慣れていません (1990 年代初頭に使用し、2000 年代初頭に使用した後)。Haskell Wiki に示されている IO モナドの例にほぼ直接類似したパターンを使用するコードを作成しようとしています。

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

(はい、これは IO の GHC 実装ではなく、単にそれを理解するための手段であることを知っています。) その理由は、私のアプリケーション (ゲーム) で、RealWorldここでの 2 つの異なる置換でこれを行う 2 つのパターンがあるためです。 . 1 つはゲームの状態であり、もう 1 つは単なるStdGen乱数シードです。もちろん、次のようなタイプのペアが 2 つあります。

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

(はい、それらを 2 つのパラメーターを持つ 1 つのペアに抽象化することはできますが、私はそれを理解していません。) もちろん、 myWithGS aおよびWithRNG 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 シードを返します。私のメソッドの大部分はこのようなもので ( WithRNGorWithGSを使用)、連鎖状態を使用し、時には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)。

ただし、型シノニムからモナドを作成することはできません。(私は TypeSynonymInstances を試しましたが、 " instance Monad WithRNG where" またはパラメーターを使用しても機能しないようです。 a を使用するnewtypeと、役に立たない醜い構文が追加されるようです。) State Monad を十分に理解することができませんでした。どちらかを使用して同等のメソッドを作成します。しかし、私が成功したとしても、State 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

考え、提案、参照、そしてあなたの時間をありがとう!

4

1 に答える 1

9

またはStateを使用せずにモナドを使用できます。状態を渡す関数をnewtypeに直接ラップするだけです。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内で使用するのに適した値を作成します。State次に、モナドの使用が終了したら、モナドを同等の状態渡し関数に戻すことができます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関数が行うことは、入力をStatenewtype でラップし、 を使用mapMしてからアンラップすることだけです。

于 2013-04-16T04:51:41.000 に答える