2013-04-16 27 views
6

Xin chào folks,Tạo monads analoguous cho IO đơn nguyên với nhà nước xích

Tôi khá mới để Haskell một lần nữa trong năm nay (sau khi sử dụng nó trong đầu những năm 1990 và sau đó một lần nữa vào đầu những năm 00). Tôi đang cố gắng để viết một số mã có sử dụng một mô hình được analoguous gần như trực tiếp đến example IO monad shown on the Haskell Wiki:

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

(Vâng, tôi biết đây không phải là việc thực hiện GHC của IO, nhưng chỉ đơn thuần là một phương tiện để hiểu biết nó .) Lý do là, trong ứng dụng của tôi (một trò chơi), bây giờ tôi có hai mẫu làm điều này với hai thay thế khác nhau của RealWorld tại đây. Trong một, đó là trạng thái của trò chơi, và mặt khác, nó chỉ là một hạt giống số ngẫu nhiên StdGen. Tôi tất nhiên bây giờ có hai cặp loại như thế này:

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

(Vâng, tôi có thể tóm tắt chúng thành một cặp với hai tham số nhưng tôi đã không nhận xung quanh nó.) Bạn có thể thấy, tất nhiên, rằng các loại WithGS aWithRNG a của tôi (loại từ đồng nghĩa) chính xác tương tự như IO a ở trên.

Vì vậy, đây là một ví dụ đơn giản của mã làm việc thực tế mà tôi bây giờ có:

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

Điều này tạo ra một cặp ngẫu nhiên trong một phạm vi nhất định, và trả về hạt RNG thức. Một tỷ lệ lớn các phương pháp của tôi là như thế này (sử dụng WithRNG hoặc WithGS), sử dụng trạng thái xích, đôi khi thậm chí nhận được tối đa r4 hoặc r6 (hoặc gs4, v.v.). Tôi muốn viết ví dụ này giống như thế này ...

-- (Not working example) 
randomPosSt (w, h) = do 
    x <- randomR (0, w - 1) 
    y <- randomR (0, h - 1) 
    return (x, y) 

... nhưng có chữ ký và ngữ nghĩa chính xác giống nhau. Điều này có vẻ như nó nên có thể sau khi hướng dẫn nói trên mang đến cho ví dụ này:

(>>=) :: IO a -> (a -> IO b) -> IO b 
(action1 >>= action2) world0 = 
    let (a, world1) = action1 world0 
     (b, world2) = action2 a world1 
    in (b, world2) 

này, như bạn có thể thấy, gần như là chính xác những gì tôi đang làm ở trên (một khi bạn thay thế "let" cho "where" ký hiệu).

Tuy nhiên, tôi không thể tạo một Monad ra khỏi một từ đồng nghĩa loại. (Tôi đã thử TypeSynonymInstances nhưng nó không có vẻ làm việc với một trong hai "instance Monad WithRNG where" hoặc sử dụng một tham số. Sử dụng một newtype cũng dường như thêm cú pháp xấu xí vô dụng.) Tôi đã không thể tìm ra nhà nước Monad tốt đủ để thực hiện một phương pháp tương đương bằng cách sử dụng một trong hai. Mặc dù tôi đã thành công, tuy nhiên, việc triển khai Tiểu bang dường như sử dụng xấu xí "get" và "put" s (và "runState" vv) và làm cho mã ít hơn có thể đọc được, không nhiều hơn.

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

Sau tất cả điều này, tôi đã kết luận rằng tôi đang làm điều gì đó sai, hiểu nhầm điều gì đó hoặc không thể làm những gì tôi muốn làm. Tôi sắp sửa nói "tốt, bạn không cần phải tìm ra cách sửa đổi mã của bạn để làm cho trạng thái được chuyển qua được xử lý tự động, vì nó hoạt động tốt" và bỏ cuộc rồi tôi nghĩ mình sẽ hỏi ở đây (lần đầu tiên tôi khám phá). Tôi thích một giải pháp thanh lịch hơn.

Tôi cũng tìm một giải pháp tinh tế hơn sẽ cho tôi chức năng này tôi sử dụng "miễn phí:"

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

Thanks cho bất kỳ suy nghĩ, đề nghị, tài liệu tham khảo và thời gian của bạn!

+2

Trước khi tôi trả lời, bạn có biết về đơn vị 'State' không? –

+0

@GabrielGonzalez Thật vậy, tôi đã nghe nói về nhà nước Monad: "Tôi đã không thể tìm ra Monad nhà nước cũng đủ để thực hiện một phương pháp tương đương bằng cách sử dụng một trong hai. Ngay cả khi tôi đã thành công, tuy nhiên, việc thực hiện Monad Nhà nước có vẻ sử dụng xấu "lấy" và "đặt" s (và "runStates" vv) và làm cho mã ít đọc được hơn, không nhiều hơn. " –

+1

Trước khi tôi trả lời, bạn có biết về biến thể đơn nguyên ['RandT'] (http://hackage.haskell.org/packages/archive/MonadRandom/0.1.8/doc/html/Control-Monad-Random.html) chưa ? –

Trả lời

9

Bạn có thể sử dụng đơn vị State mà không cần sử dụng get hoặc put. Chỉ cần quấn chức năng đi qua tiểu bang của bạn trực tiếp trong 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) 

Bí quyết là để quan sát các loại nhà xây dựng State:

State :: (s -> (a, s)) -> State s a 

.. và randomR (lo, hi) chỉ có đúng loại để được bao bọc trực tiếp trong số State:

randomR (1, 6)   :: StdGen -> (Int, StdGen) 
StateT $ randomR (1, 6) :: State StdGen Int 

Nhà xây dựng State có chức năng chuyển trạng thái và tạo thành giá trị hợp lý để sử dụng trong một đơn vị State. Sau đó, khi bạn đang thực hiện bằng cách sử dụng đơn nguyên bạn có thể chuyển đổi các đơn nguyên trở lại chức năng nhà nước qua tương đương với sử dụng runState:

runState :: State s a -> (s -> (a, s)) 

runState (randomPos (5, 6)) :: StdGen -> ((Int, Int), StdGen) 

này là trong thực tế như thế nào RandT công trình, bằng cách gói tất cả các chức năng ngẫu nhiên qua máy phát điện trong một đơn vị trạng thái và RandT tương đương với StateT StdGen dưới mui xe.

Ngoài ra, như bạn nói, việc xây dựng monadic sẽ cung cấp cho bạn phiên bản ánh xạ miễn phí:

mapRandom 
    :: (a -> (StdGen -> (b , StdGen))) 
    -> ([a] -> (StdGen -> ([b], StdGen))) 
mapRandom f xs = runState $ mapM (State . f) xs 

này được vì loại mapM (khi chuyên để State) là:

mapM :: (a -> State s b) -> [a] -> State s [b] 

Vì vậy, tất cả các chức năng trên mapRandom làm là bọc đầu vào trong một newtype State, sử dụng mapM, và sau đó unwrap nó.

+0

Chà. Cảm ơn các câu trả lời chi tiết. Tôi sẽ thử điều này trên cả hai loại của tôi và xem nó trông như thế nào trong mã của tôi. Tôi đã có hai câu hỏi tiếp theo: Tại sao trong câu trả lời của bạn, bạn tự tạo một ví dụ State newtype và Monad thay vì sử dụng một lần nhập? Và, nó không thể tránh được "State" và "runState" wrapper/unwrappers để có được một cái gì đó thanh lịch hơn như cú pháp thanh lịch của một đơn vị (giả định) IO? –

+1

@SoftwareEngineer Đó là vì không còn triển khai chính thức phiên bản 'State' biến đổi không đơn nguyên trong thư viện chuẩn. Nếu bạn kiểm tra 'biến' và' mtl', chúng bây giờ vừa thực thi 'State s' như một từ đồng nghĩa của kiểu' StateT s Identity', và thay cho hàm tạo 'State' chúng cung cấp hàm' state' hoạt động cùng một cách. –

+0

Có, tôi đã nhận thấy rằng các đơn vị nhà nước đã được sửa đổi. Một câu hỏi cuối cùng: Tại sao đơn vị IO lại đặc biệt? Nó dường như được thực hiện chính xác như các đơn vị nhà nước ở trên nhưng không cần phải có wrapper newtype/unwrapper. Tôi rất muốn làm những điều đó bằng cách nào đó. Cảm ơn một lần nữa. (Không có ý tưởng tại sao nó sẽ không lấy AT Gabriel của tôi hoặc AT GabrielGonzalez.) –

Các vấn đề liên quan