2012-06-15 5 views
6

Ai đó có thể mô tả cách hoạt động của hàm tạo và hàm kiểu sau?Thế hệ ngẫu nhiên Haskell

type Rand a = State StdGen a 

getRandom :: (Random a) => Rand a 
getRandom = get >>= (\r -> let (a,g) = random r in (put g) >> (return a)) 

runRand :: Int -> Rand a -> a 
runRand n r = evalState r $ mkStdGen n 

runRandIO :: Rand a -> IO a 
runRandIO r = randomIO >>= (\rnd -> return $ runRand rnd r) 

getRandoms :: (Random a) => Int -> Rand [a] 
getRandoms n = mapM (\_ -> getRandom) [1..n] 

Trả lời

8

Hãy bắt đầu ngay từ đầu:

type Rand a = State StdGen a 

Dòng này nói với bạn rằng Rand a là một loại từ đồng nghĩa với một loại State, mà nhà nước được đưa ra bởi StdGen và có giá trị cuối cùng là loại a. Điều này sẽ được sử dụng để lưu trữ trạng thái của trình tạo số ngẫu nhiên giữa mỗi yêu cầu cho một số ngẫu nhiên.

Mã cho getRandom có thể được chuyển đổi thành ký hiệu làm:

getRandom :: (Random a) => Rand a 
getRandom = do 
    r <- get     -- get the current state of the generator 
    let (a,g) = random r in do -- call the function random :: StdGen -> (a, StdGen) 
    put g     -- store the new state of the generator 
    return a     -- return the random number that was generated 

Chức năng runRand mất một hạt giống ban đầu n và một giá trị r loại Rand a (trong đó, hãy nhớ rằng, chỉ là một từ đồng nghĩa với State StdGen a). Nó tạo ra một máy phát điện mới với mkStdGen n và cấp phát cho evalState r. Hàm evalState chỉ đánh giá giá trị trả về của loại State s a, bỏ qua trạng thái.

Một lần nữa, chúng ta có thể chuyển đổi runRandIO vào do ký hiệu:

runRandIO :: Rand a -> IO a 
runRandIO r = do 
    rnd <- randomIO  -- generate a new random number using randomIO 
    return (runRand rnd r) -- use that number as the initial seed for runRand 

Cuối cùng, getRandoms mất một số n đại diện cho số các giá trị ngẫu nhiên mà bạn muốn tạo ra. Nó tạo danh sách [1..n] và áp dụng getRandom vào danh sách. Lưu ý rằng các giá trị thực tế trong [1..n] không được sử dụng (bạn có thể biết vì hàm lambda bắt đầu bằng \_ -> ...). Danh sách chỉ ở đó để có thứ gì đó với số lượng phần tử chính xác. Vì getRandom trả về một giá trị monadic, chúng tôi sử dụng mapM để lập bản đồ trong danh sách, khiến trạng thái (ví dụ: StdGen) được luồn chính xác qua từng cuộc gọi đến getRandom.

5

Ý tưởng cơ bản là đơn giản - để tạo số giả ngẫu nhiên, bạn cần duy trì một số trạng thái giữa các cuộc gọi hàm. Vì vậy, loại Rand a được định nghĩa là "a cùng với trạng thái cần thiết cho ngẫu nhiên".

Trạng thái được lưu trữ bằng cách sử dụng State monad. Điều này cung cấp hai hành động chính-- getput, thực hiện chính xác những gì họ nghe. Vì vậy, getRandom chỉ tra cứu trạng thái hiện tại và sau đó gọi hàm random. Hàm này trả về hai giá trị: giá trị ngẫu nhiên và trạng thái mới. Sau đó, bạn chỉ cần put trạng thái mới và bọc giá trị kết quả.

runRand cho phép bạn mở một giá trị "ngẫu nhiên" cho một hạt giống. evalState cho phép bạn thực hiện tính toán trạng thái (nghĩa là giá trị loại State s a hoặc, trong trường hợp này là Rand a) cho trạng thái ban đầu và sau đó chỉ loại bỏ trạng thái cuối cùng, chỉ cho bạn kết quả. Vì vậy, điều này cho phép bạn chạy một số Rand a với một hạt giống đã cho và chỉ trả lại giá trị kết quả. Giá trị chỉ có thể có loại a, thay vì Rand a vì giá trị này sẽ luôn cung cấp cho bạn kết quả tương tự cho cùng một hạt giống.

runRandomIO chỉ làm điều tương tự ngoại trừ nhận hạt giống dựa trên một số trạng thái toàn cầu trong IO.

getRandoms chỉ mang đến cho bạn danh sách các giá trị Rand a bằng cách gọi getRandom cho mọi thành phần trong danh sách [1..n] (bỏ qua số thực).