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