2012-09-03 21 views
7

Tôi đang viết mô phỏng trò chơi bài trong Haskell với nhiều đối thủ AI. Tôi muốn có một chức năng chính với một số thứ như GameParameters -> [AIPlayer] -> GameOutcome. Nhưng tôi muốn nghĩ về chức năng chính là một "chức năng thư viện" để tôi có thể viết một AIPlayer mới với việc thay đổi bất kỳ thứ gì khác.AI cồng kềnh trong Haskell

Vì vậy, tôi đã nghĩ đến việc tạo AIPlayer kiểu chữ, do đó hàm chính sẽ trở thành AIPlayer a => GameParameters -> [a] -> GameOutcome. Nhưng điều này chỉ cho phép một loại AI được chèn vào. Vì vậy, để chèn nhiều AIPlayers trong một trò chơi, tôi cần phải xác định một wrappertype

AIWrapper = P1 AIPlayer1 | P2 AIPlayer2 | ... 

instance AIWrapper AIPlayer where 
    gameOperation (P1 x) = gameOperation x 
    gameOperation (P2 x) = gameOperation x 
    ... 

Tôi không cảm thấy hài lòng với loại wrapper này và tôi cảm thấy như phải có cái gì tốt hơn thế này, hay tôi sai?

+1

Có lẽ những gì bạn đang tìm kiếm là một bộ sưu tập không đồng nhất. Xem ví dụ 'Showable' tại http://www.haskell.org/haskellwiki/Heterogenous_collections – ErikR

+2

Xem https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ để thảo luận về làm thế nào để biến đổi một nhu cầu cho các danh sách không đồng nhất thành một thiết kế tốt. –

Trả lời

17

Có vẻ như bạn có thể đang trên đường đến những gì Luke Palmer gọi là existential type class antipattern. Đầu tiên, chúng ta hãy giả sử rằng bạn có một vài kiểu dữ liệu AI-chơi:

data AIPlayerGreedy = AIPlayerGreedy { gName :: String, gFactor :: Double } 
data AIPlayerRandom = AIPlayerRandom { rName :: String, rSeed :: Int } 

Bây giờ, bạn muốn làm việc với những bằng cách làm cho họ cả hai trường hợp của một lớp loại:

class AIPlayer a where 
    name  :: a -> String 
    makeMove :: a -> GameState -> GameState 
    learn :: a -> GameState -> a 

instance AIPlayer AIPlayerGreedy where 
    name  ai = gName ai 
    makeMove ai gs = makeMoveGreedy (gFactor ai) gs 
    learn ai _ = ai 

instance AIPlayer AIPlayerRandom where 
    name  ai = rName ai 
    makeMove ai gs = makeMoveRandom (rSeed ai) gs 
    learn ai gs = ai{rSeed = updateSeed (rSeed ai) gs} 

chí này làm việc nếu bạn chỉ có một giá trị như vậy, nhưng có thể khiến bạn gặp vấn đề, như bạn đã nhận thấy. Tuy nhiên, lớp loại này mua cho bạn cái gì? Trong ví dụ của bạn, bạn muốn xử lý một tập hợp các phiên bản khác nhau của AIPlayer thống nhất. Vì bạn không biết các loại cụ thể nào sẽ có trong bộ sưu tập, bạn sẽ không bao giờ có thể gọi bất cứ thứ gì như gFactor hoặc rSeed; bạn sẽ chỉ có thể sử dụng các phương thức được cung cấp bởi AIPlayer. Vì vậy, tất cả bạn cần là một tập hợp những chức năng, và chúng tôi có thể đóng gói những lên trong một loại đồng bằng cũ dữ liệu:

data AIPlayer = AIPlayer { name  :: String 
         , makeMove :: GameState -> GameState 
         , learn :: GameState -> AIPlayer } 

greedy :: String -> Double -> AIPlayer 
greedy name factor = player 
    where player = AIPlayer { name  = name 
          , makeMove = makeMoveGreedy factor 
          , learn = const player } 

random :: String -> Int -> AIPlayer 
random name seed = player 
    where player = AIPlayer { name  = name 
          , makeMove = makeMoveRandom seed 
          , learn = random name . updateSeed seed } 

Một AIPlayer, sau đó, là một bộ sưu tập các bí quyết: tên gọi của nó, làm thế nào để thực hiện một động thái, và làm thế nào để tìm hiểu và sản xuất một máy nghe nhạc AI mới. Các kiểu dữ liệu của bạn và các cá thể của chúng đơn giản trở thành các hàm tạo ra AIPlayer s; bạn có thể dễ dàng đặt mọi thứ trong một danh sách, vì [greedy "Alice" 0.5, random "Bob" 42] được nhập tốt: đó là loại [AIPlayer].


Bạn thể, đó là sự thật, gói lên trường hợp đầu tiên của bạn với một kiểu hiện sinh:

{-# LANGUAGE ExistentialQuantification #-} 
data AIWrapper = forall a. AIPlayer a => AIWrapper a 

instance AIWrapper a where 
    makeMove (AIWrapper ai) gs = makeMove ai gs 
    learn (AIWrapper ai) gs = AIWrapper $ learn ai gs 
    name  (AIWrapper ai) = name ai 

Bây giờ, [AIWrapper $ AIPlayerGreedy "Alice" 0.5, AIWrapper $ AIPlayerRandom "Bob" 42] nổi gõ: đó là kiểu [AIWrapper]. Nhưng như bài viết của Luke Palmer ở ​​trên quan sát, điều này không thực sự mua cho bạn bất cứ điều gì, và trong thực tế làm cho cuộc sống của bạn phức tạp hơn. Vì nó tương đương với trường hợp đơn giản, không có kiểu, không có lợi thế; các tính tồn tại chỉ cần thiết nếu cấu trúc bạn sắp xếp phức tạp hơn.

+3

+1 Giải thích rất hay! – Landei

+0

cảm ơn, đó chính xác là loại giải thích mà tôi đang tìm kiếm – Ingdas

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