2010-07-30 33 views
10

Cuối cùng tôi đã có một giữ về cách sử dụng monads (không biết nếu tôi hiểu họ ...), nhưng mã của tôi là không bao giờ rất thanh lịch. Tôi đoán là từ một thiếu hiểu về cách tất cả các chức năng trên Control.Monad thực sự có thể giúp đỡ. Vì vậy, tôi nghĩ rằng nó sẽ là tốt đẹp để yêu cầu cho lời khuyên về điều này trong một đoạn mã cụ thể bằng cách sử dụng monad nhà nước.Lời khuyên cho mã thanh lịch hơn với monads?

Mục đích của mã là tính toán nhiều loại lượt đi ngẫu nhiên và đó là điều tôi đang cố gắng làm trước một điều phức tạp hơn. Vấn đề là tôi có hai phép tính stateful cùng một lúc, và tôi muốn biết làm thế nào để soạn chúng với sự sang trọng:

  1. các chức năng đó cập nhật các bộ tạo số ngẫu nhiên là một cái gì đó kiểu Seed -> (DeltaPosition, Seed)
  2. Chức năng cập nhật vị trí của khung tập đi ngẫu nhiên là loại DeltaPosition -> Position -> (Log, Position) (trong đó Log chỉ là một số cách để tôi báo cáo vị trí hiện tại của người đi bộ ngẫu nhiên là gì).

Những gì tôi đã làm điều này là:

Tôi có một chức năng để soạn này hai tính stateful:

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

và sau đó tôi biến nó thành một chức năng soạn trạng thái:

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 

Và sau đó tôi có điều đơn giản nhất, ví dụ: trình đi bộ ngẫu nhiên sẽ chỉ tổng hợp một số ngẫu nhiên vào vị trí hiện tại của nó:

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 

và một chức năng để làm điều này lặp đi lặp lại:

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

Và sau đó, nếu tôi tải này trong ghci và chạy:

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

tôi nhận được những gì tôi muốn, mà là một danh sách các vị trí bị người đi bộ ngẫu nhiên chiếm đóng. Nhưng ... tôi cảm thấy phải có một cách thanh lịch hơn để làm điều này. Tôi có hai câu hỏi:

  1. Tôi có thể viết lại các chức năng đó theo cách "đơn thuần" hơn, sử dụng các chức năng thông minh từ Control.Monad?

  2. Có một mô hình chung về việc kết hợp các trạng thái như thế này có thể được sử dụng không? Điều này có liên quan gì đến máy biến áp đơn nguyên hay thứ gì đó như thế không?

+2

Bằng cách này, bạn nên tránh sử dụng hàm tạo dữ liệu 'State', vì kế thừa' mtl' ('monads-fd'),' State' được định nghĩa theo 'StateT' và do đó hàm tạo dữ liệu' State' không tồn tại. –

+0

@TravisBrown Trên thực tế, 'monads-fd' không được hỗ trợ cho' mtl'. (Nhận ra rằng bình luận của bạn là 5 tuổi.) – crockeea

Trả lời

11

Cập nhật: Tôi có nên nói rằng có thực sự là một cách rất đẹp để làm điều này mà không yêu cầu State hoặc monads tại tất cả:

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) 

Nó hoạt động như mong muốn:

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

Nếu bạn không cam kết với ý tưởng "soạn" hai tính toán trạng thái riêng biệt, bạn có thể thực hiện điều tương tự nhiều thẳng thắn hơn:

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) 

này sẽ cho kết quả tương tự như ví dụ của bạn:

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

Cách tiếp cận này (làm tất cả các thao tác nhà nước trong một đơn nguyên đơn thay vì cố gắng để soạn một State AState B) dường như với tôi giống như giải pháp thanh lịch nhất.


Cập nhật: Để trả lời câu hỏi của bạn về việc sử dụng máy biến áp đơn lẻ để ngăn xếp State monads: nó chắc chắn có thể. Chúng tôi có thể viết những điều sau, ví dụ:

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 

Chúng tôi cũng có thể xếp chồng theo thứ tự ngược lại.

Phiên bản này một lần nữa sản xuất cùng một đầu ra, nhưng theo ý kiến ​​của tôi, phiên bản không StateT là một chút rõ ràng hơn.

1

Cách thông thường để soạn 2 đơn vị (và cách duy nhất cho hầu hết các đơn vị) là với máy biến áp đơn nguyên, nhưng với các đơn vị khác nhau State bạn có nhiều tùy chọn hơn. Ví dụ: bạn có thể sử dụng các hàm này:

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')) 
Các vấn đề liên quan