2013-04-16 4 views
6

Здравствуйте люди,Создание монады к аналогичной IO Монады с цепью, состояние

Я довольно новыми для Haskell снова в этом году (после того, как использовать его в начале 1990-х годов, а затем в начале 00-х). Я пытаюсь написать код, который использует шаблон, который почти непосредственно к аналогичным example IO monad shown on the Haskell Wiki:

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

(Да, я знаю, что это не реализация 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 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 

Это создает случайные пары в заданном диапазоне, и возвращает окончательное семени ГСЧ. Большой процент моих методов подобен этому (используя WithRNG или WithGS), используя прикованное состояние, иногда даже поднимаясь до r4 или 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», ни с использованием параметра. Использование newtype также, похоже, добавит бесполезный уродливый синтаксис.) Я не смог хорошо определить состояние штата Монада достаточно, чтобы использовать эквивалентный метод. Однако, даже если бы я преуспел, реализация государственной Монады показала бы уродливые «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 

Спасибо за любые мысли, предложения, ссылки и ваше время!

+2

Прежде чем я отвечу, вы знаете о монаде «Государство»? –

+0

@GabrielGonzalez Действительно, я слышал о государственной монаде: «Я не смог правильно определить государственную Монаду, чтобы использовать эквивалентный метод, используя это. Даже если бы мне удалось, однако, реализация государственной монады показалась бы использовать уродливые «get» и «put» s (и «runStates» и т. д.) и сделать код менее читаемым, а не больше ». –

+1

Прежде чем я отвечу, вы знаете о монад-трансформаторе ['RandT'] (http://hackage.haskell.org/packages/archive/MonadRandom/0.1.8/doc/html/Control-Monad-Random.html) ? –

ответ

9

Вы можете использовать монаду State без использования get или put. Просто обернуть государственные преходящие функции непосредственно в 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. Затем, когда вы закончите с помощью монады вы можете преобразовать монаду обратно в эквивалентное состоянии прохождения функции с помощью 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 выполняет функцию обертывания ввода в State newtype, используйте mapM, а затем разверните его.

+0

Ничего себе. Спасибо за подробный ответ. Я собираюсь попробовать это на обоих моих типах и посмотреть, как он выглядит в моем коде. У меня было два вопроса: почему в вашем ответе вы создаете экземпляр State newtype и Monad самостоятельно вместо использования импорта? И разве не возможно избежать обертки/разворачивания «State» и «runState», чтобы получить что-то более элегантное, как элегантный (предположительно) элегантный шрифт монады монахини IO? –

+1

@SoftwareEngineer Это связано с тем, что официальная реализация не-монад-трансформаторной версии «State» в стандартных библиотеках. Если вы проверите «трансформаторы» и «mtl», теперь они реализуют «State s» как синоним типа «StateT s Identity», а вместо конструктора «State» они предоставляют функцию «state», которая ведет себя так же. –

+0

Да, я заметил, что государственная монада была изменена. Один последний вопрос: почему особая монада IO? Похоже, что он реализован точно так же, как и государственная монада, но не нуждается в обертке/распаковке newtype. Мне бы хотелось как-то покончить с этим. Еще раз спасибо. (Не знаю, почему это не займет мой AT Gabriel или AT GabrielGonzalez.) –

Смежные вопросы