2010-07-30 5 views
10

Наконец-то я понял, как использовать монады (не знаю, понимаю ли я их ...), но мой код никогда не бывает очень изящным. Я думаю, из-за отсутствия хватки, как все эти функции на Control.Monad действительно могут помочь. Поэтому я подумал, что было бы неплохо попросить совета по этому поводу в определенном фрагменте кода, используя государственную монаду.Советы для более элегантного кода с монадами?

Целью кода является вычисление многих видов случайных блужданий, и это то, что я пытаюсь сделать перед чем-то более сложным. Проблема заключается в том, что у меня есть два вычисления с состоянием в то же время, и я хотел бы знать, как составить их с элегантностью:

  1. функция, которая обновляет генератор случайных чисел является то типа Seed -> (DeltaPosition, Seed)
  2. Функция, которая обновляет положение случайного ходока, является чем-то вроде DeltaPosition -> Position -> (Log, Position) (где Log - это всего лишь способ сообщить, что представляет собой текущее положение случайного ходока).

Что я сделал это:

У меня есть функция, чтобы составить эти два отслеживание состояния вычислений:

composing :: (g -> (b, g)) -> (b -> s -> (v,s)) -> (s,g) -> (v, (s, g)) 
composing generate update (st1, gen1) = let (rnd, gen2) = generate gen1 
              (val, st2) = update rnd st1 
             in (val, (st2, gen2)) 

и затем я превратить его в функцию, которая сочинить состояние:

stateComposed :: State g b -> (b -> State s v) -> State (s,g) v 
stateComposed rndmizer updater = let generate = runState rndmizer 
            update x = runState $ updater x 
           in State $ composing generate update 

И вот у меня есть простейшая вещь, например, случайный ходок, который просто суммирует случайное число с его текущим положением:

update :: Double -> State Double Double 
update x = State (\y -> let z = x+y 
         in (z,z)) 

generate :: State StdGen Double 
generate = State random 

rolling1 = stateComposed generate update 

и функцию, чтобы сделать это несколько раз:

rollingN 1 = liftM (:[]) rolling1 
rollingN n = liftM2 (:) rolling1 rollings 
    where rollings = rollingN (n-1) 

И потом, если я загружаю это в ghci и запуск:

*Main> evalState (rollingN 5) (0,mkStdGen 0) 
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918] 

Я получаю то, что я хочу, что является список позиций, занимаемых случайным ходоком. Но ... я чувствую, что должен быть более элегантный способ сделать это. У меня есть два вопроса:

  1. Могу ли я переписать эти функции в более «монадической» способом, с использованием интеллектуальных функций от Control.Monad?

  2. Есть ли общий шаблон об объединении состояний, подобных этому, которые можно использовать? Это имеет какое-то отношение к трансформаторам монады или тому подобное?

+2

Кстати, это хорошая идея, чтобы избежать с помощью конструктора данных 'State', так как в' преемнику mtl' (в 'монады-fd'),' State' определяется в терминах 'StateT', и поэтому конструктор данных' State' не существует. –

+0

@TravisBrown На самом деле, 'monads-fd' устарел в пользу' mtl'. (Признавая, что вашему комментарию 5 лет.) – crockeea

ответ

11

Обновление: Я хотел бы упомянуть, что есть на самом деле гораздо лучше способ сделать это, что не требует State или монады на все:

takeStep :: (Double, StdGen) -> (Double, StdGen) 
takeStep (p, g) = let (d, g') = random g in (p + d, g') 

takeSteps n = take n . tail . map fst $ iterate takeStep (0, mkStdGen 0) 

Он работает по желанию:

*Main> takeSteps 5 
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918] 

Если вы не привержены идее «составления» двух отдельных вычислений с учетом состояния, вы можете выполнить то же самое гораздо более прямолинейно:

takeStep :: State (Double, StdGen) Double 
takeStep = do 
    (pos, gen) <- get 
    let (delta, gen') = random gen 
    let pos' = pos + delta 
    put (pos', gen') 
    return pos' 

takeSteps n = evalState (replicateM n takeStep) (0, mkStdGen 0) 

Это производит такой же результат, как ваш пример:

*Main> takeSteps 5 
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918] 

Этот подход (делает все государственные манипуляции в одной монады вместо того, чтобы пытаться составить State A и State B) кажется мне самым элегантным решением.


Update: Для того, чтобы ответить на ваш вопрос об использовании монады трансформаторов стек State монады: это, конечно, возможно. Мы можем написать следующее, например:

update' :: (Monad m) => Double -> StateT Double m Double 
update' x = StateT $ \y -> let z = x + y in return (z, z) 

generate' :: (Monad m) => StateT StdGen m Double 
generate' = StateT $ return . random 

takeStep' :: StateT Double (State StdGen) Double 
takeStep' = update' =<< lift generate' 

takeSteps' n = evalState (evalStateT (replicateM n takeStep') 0) $ mkStdGen 0 

Мы также можем выполнять штабелирование в обратном порядке.

Эта версия снова производит тот же вывод, но, по моему мнению, версия не StateT немного понятна.

1

Обычный способ составить 2 монады (и единственный способ для большинства монад) - с монадными трансформаторами, но с разными монами State у вас есть больше возможностей. Например: вы могли бы использовать эти функции:

leftState :: State a r -> State (a,b) r 
leftState act = state $ \ ~(a,b) -> let 
    (r,a') = runState act a 
    in (r,(a',b)) 

rightState :: State b r -> State (a,b) r 
rightState act = state $ \ ~(a,b) -> let 
    (r,b') = runState act b 
    in (r,(a,b'))