2010-10-15 37 views
14

Haskell là một ngôn ngữ lập trình hàm thuần túy.Haskell và Tiểu bang

Câu hỏi của tôi là: Ưu điểm và nhược điểm của việc sử dụng Haskell để giải quyết các vấn đề liên quan đến nhiều trạng thái, ví dụ lập trình GUI hoặc lập trình trò chơi là gì?

Cũng là câu hỏi phụ: phương pháp nào có để xử lý trạng thái theo cách chức năng?

Xin cảm ơn trước.

+0

http://www.haskell.org/all_about_monads/html/statemonad.html –

+4

Trạng thái xử lý bằng ngôn ngữ chức năng liên quan đến việc chuyển trạng thái xung quanh các chức năng. Monads đơn giản hóa điều này. – tylermac

+0

@tylermac tôi không bao giờ có thể hiểu được monads, tốt, tôi không thể nói rằng tôi ngu ngốc, ít nhất tôi là BS trong CS, nhưng monads ... bạn có biết hướng dẫn tốt? – Andrey

Trả lời

17

Tôi sẽ trả lời câu hỏi thứ hai của bạn đầu tiên. Có nhiều cách để xử lý trạng thái có thể thay đổi trong Haskell (và các ngôn ngữ FP khác). Trước hết, Haskell hỗ trợ trạng thái có thể thay đổi trong IO, thông qua các cấu trúc IORefmvar. Sử dụng chúng sẽ cảm thấy rất quen thuộc với các lập trình viên từ các ngôn ngữ bắt buộc. Ngoài ra còn có các phiên bản chuyên biệt như STRefTMVar, cũng như các mảng có thể thay đổi, con trỏ và nhiều dữ liệu có thể thay đổi khác. Hạn chế lớn nhất là chúng thường chỉ có sẵn trong IO hoặc một đơn chuyên biệt hơn.

Cách phổ biến nhất để mô phỏng trạng thái bằng ngôn ngữ hàm là chuyển trạng thái rõ ràng làm đối số hàm và giá trị trả về. Ví dụ:

randomGen :: Seed -> (Int, Seed) 

Đây randomGen lấy tham số hạt giống và trả về một hạt giống mới. Mỗi khi bạn gọi nó, bạn cần phải theo dõi hạt giống cho lần lặp tiếp theo. Kỹ thuật này luôn luôn có sẵn cho nhà nước đi qua, nhưng nó nhanh chóng được tẻ nhạt.

Có lẽ cách tiếp cận Haskell phổ biến nhất là sử dụng một đơn nguyên để gói gọn trạng thái này. Chúng tôi có thể thay thế randomGen với điều này:

-- a Random monad is simply a Seed value as state 
type Random a = State Seed a 

randomGen2 :: Random Int 
randomGen2 = do 
    seed <- get 
    let (x,seed') = randomGen seed 
    put seed' 
    return x 

Bây giờ bất kỳ chức năng mà cần một PRNG có thể chạy trong đơn nguyên ngẫu nhiên để yêu cầu chúng khi cần thiết. Bạn chỉ cần cung cấp một trạng thái ban đầu và tính toán.

runRandomComputation :: Random a -> Seed -> a 
runRandomComputation = evalState 

(lưu ý có các hàm rút ngắn đáng kể định nghĩa ngẫu nhiênGen2; tôi chọn phiên bản rõ ràng nhất).

Nếu tính toán ngẫu nhiên của bạn cũng cần quyền truy cập vào IO, thì bạn sử dụng phiên bản biến áp đơn nguyên của Nhà nước, StateT.

Lưu ý đặc biệt là ST đơn nguyên, về cơ bản cung cấp cơ chế đóng gói các đột biến IO cụ thể cách xa phần còn lại của IO. ST monad cung cấp các STREF, là một tham chiếu có thể thay đổi được với dữ liệu và cũng có thể có các mảng có thể thay đổi được.Sử dụng ST, có thể xác định những thông tin như sau:

randomList :: Seed -> [Int] 

trong đó [Int] là một danh sách vô hạn các số ngẫu nhiên (nó sẽ chu kỳ cuối cùng tùy thuộc vào PSRG) của bạn từ hạt giống ban đầu mà bạn cung cấp.

Cuối cùng, có Functional Reactive Programming. Có lẽ các thư viện nổi bật nhất hiện nay là YampaReactive, nhưng những thư viện khác cũng đáng xem. Có một số cách tiếp cận đến trạng thái có thể thay đổi trong các triển khai khác nhau của FRP; từ việc sử dụng chúng một cách nhẹ nhàng, chúng thường có vẻ tương tự như một khái niệm về khung tín hiệu như trong QT hoặc Gtk + (ví dụ như thêm người nghe cho các sự kiện).

Bây giờ, cho câu hỏi đầu tiên. Đối với tôi, lợi thế lớn nhất là trạng thái có thể thay đổi được tách biệt với mã khác ở cấp loại. Điều này có nghĩa là mã không thể vô tình sửa đổi trạng thái trừ khi nó được đề cập rõ ràng trong chữ ký loại. Nó cũng cho phép kiểm soát rất tốt trạng thái chỉ đọc so với trạng thái có thể thay đổi (Reader monad vs. State monad). Tôi tìm thấy nó rất hữu ích để cấu trúc mã của tôi theo cách này, và nó rất hữu ích để có thể nói chỉ từ chữ ký loại nếu một chức năng có thể được đột biến nhà nước bất ngờ.

Cá nhân tôi không thực sự có bất kỳ đặt chỗ nào về việc sử dụng trạng thái có thể thay đổi trong Haskell. Khó khăn lớn nhất là nó có thể tẻ nhạt để thêm trạng thái vào cái gì đó không cần nó trước đây, nhưng điều tương tự sẽ tẻ nhạt trong các ngôn ngữ khác mà tôi đã sử dụng cho các nhiệm vụ tương tự (C#, Python).

+0

'Seed' ở đây là gì? Tôi đã thử định nghĩa nó như int ở đầu tệp của tôi ('type Seed = Int') nhưng nhận được lỗi' No instance for (Show (Random Int)) '(ban đầu tôi đã sử dụng lý thuyết mã của bạn, không làm việc, vì vậy hãy thử sao chép và dán tất cả vào và nhận được lỗi này sau đó). –

+0

Đây là mã giả hơn so với triển khai thực tế. 'Seed' là một kiểu trừu tượng biểu diễn dữ liệu phụ thuộc vào trạng thái của PRNG. Tôi đã sử dụng PNRG vì nó là một ví dụ nổi tiếng của một lớp các thuật toán stateful. Nếu bạn cần một trình tạo số ngẫu nhiên, tôi muốn giới thiệu một gói như 'mwc-random' hoặc' mersenne-random'. –

+0

Cảm ơn bạn đã trả lời - Tôi đang cố gắng thực sự hiểu moads hơn là sử dụng PRNG, nhưng tôi nghĩ đây là một ví dụ thực sự tốt. Tôi hoàn toàn bị mắc kẹt mặc dù. Tôi đã hỏi một câu hỏi SO về điều này một vài giờ trước, nhưng bây giờ thậm chí còn bối rối hơn! http://stackoverflow.com/questions/23595363/simple-haskell-monad-random-number/ –

2

Thông thường những gì bạn sẽ làm là sử dụng Bộ biến đổi đơn vị với một StateT và IO, điều này vì giao diện (GUI) cần IO để phản hồi, tuy nhiên một khi bạn đã xác định Biến áp Monad của mình trong newtype, bạn muốn làm cho chữ ký của logic trò chơi chỉ với giao diện MonadState, theo cách đó bạn vẫn có lợi ích của các thay đổi không phải IO-ness. Mã dưới đây giải thích những gì tôi muốn nói:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 
import Control.Monad.State 

data GameState = GameState { ... } deriving (Show) 
newtype GameMonad a = GameMonad (StateT GameState IO a) 
         deriving (Monad, MonadState GameState, MonadIO) 

-- This way, now you have a monad with the state of the info 
-- what would you like now is being able to modify the state, without actually 
-- having IO capabilites. 

movePlayerOnState :: (MonadState GameState) m => Position -> m() 
-- In this function we get the state out of the state monad, and then we modify 
-- with a pure function, then put the result back again 

-- Other times you would like to have the GUI API available, requiring the IO monad 
-- for that 
renderGameFromState :: MonadIO m => GameState -> m() 
-- in this method you would use liftIO method to call the GUI API 

Mã này là khá phức tạp nếu bạn không hiểu monads, nhưng quy tắc của tôi nhỏ là, tìm hiểu những gì các đơn nguyên nhà nước là cho, hiểu những gì Monad Transformers là (không có sự cần thiết phải hiểu cách họ làm việc) và cách sử dụng đơn vị StateT.

tôi có thể chỉ cho bạn một dự án Sokoban tôi đã làm với đồng đội khác mà có thể có ích, nó sử dụng ncurses như GUI, nhưng bạn có thể lấy ý tưởng về logic và làm thế nào chúng ta quản lý các quốc gia trên Game

http://github.com/roman/HaskBan

Chúc may mắn.

4

Ưu điểm và nhược điểm của việc sử dụng Haskell để giải quyết các vấn đề liên quan đến nhiều trạng thái, ví dụ lập trình GUI hoặc lập trình trò chơi là gì?

Lợi thế là, ngay cả khi bạn không đặc biệt tận dụng độ tinh khiết, Haskell chỉ đơn giản là một ngôn ngữ tốt .

Chức năng hạng nhất - không phải là một vấn đề lớn trong năm 2010, nhưng đúng vậy. Các loại đại số có khớp mẫu. Kiểm tra kiểu tĩnh mạnh mẽ với suy luận kiểu. Cú pháp sạch. Đồng thời hạng nhất, STM và song song thuần túy không có luồng. Một trình biên dịch tốt. Tấn thư viện và nhiều hơn nữa mỗi ngày. Một cộng đồng năng động, hữu ích.

Đây không phải là những quyết định tư tưởng lớn như sự tinh khiết hay lười biếng. Chúng chỉ là những ý tưởng hay.Họ là những thứ mà hầu hết ngôn ngữ có thể có, nhưng quá nhiều thì không.

+0

Bạn có thể cho tôi biết ý nghĩa của đồng thời hạng nhất không? –

4

Một đơn vị trạng thái là cách tồi tệ nhất để lập mô hình GUI hoặc trò chơi trong Haskell. Tôi nghĩ rằng lựa chọn tốt nhất thứ hai là sử dụng đồng thời trong cả hai trường hợp. Tuy nhiên, lựa chọn tốt nhất đã được đề cập bởi Paul: Lập trình phản ứng chức năng (FRP).

Cá nhân tôi ủng hộ mũi tên FRP (AFRP), mà tôi nghĩ là lần đầu tiên được triển khai dưới dạng Yampa và sau đó được chia thành các Animas hữu ích hơn một chút. Tuy nhiên, Yampa nhanh chóng đạt đến giới hạn của nó, vì vậy tôi đã viết một thư viện mạnh mẽ hơn, mang tính biểu cảm hơn được gọi là netwire, cũng có một số cải tiến về khái niệm so với trước đây.

Trong bản chất AFRP của nó là một hệ thống trạng thái chức năng. Đó là chức năng trong trạng thái đó không được mô hình hóa như thay đổi các giá trị biến, nhưng các hàm biến đổi. Điều này là sạch hơn và không yêu cầu bất kỳ chương trình bắt buộc như monads nhà nước làm.

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