2012-02-05 25 views
5

Tôi muốn sử dụng haskell để triển khai trò chơi và muốn sử dụng hệ thống các lớp loại để triển khai hệ thống mục. Nó sẽ làm việc một cái gì đó như thế này:Kiểm tra Typeclass Haskell

data Wood = Wood Int 

instance Item Wood where 
    image a = "wood.png" 
    displayName a = "Wood" 

instance Flammable Wood where 
    burn (Wood health) | health' <= 0 = Ash 
        | otherwise = Wood health' 
     where health' = health - 100 

nơi Item và các lớp học dễ cháy là một cái gì đó như thế này:

class Item a where 
    image :: a -> String 
    displayName :: a -> String 

class Flammable a where 
    burn :: (Item b) => a -> b 

Để làm điều này, tôi sẽ cần một cách để phát hiện xem một giá trị là một thể hiện của một loại lớp.

Mô-đun Data.Data cung cấp chức năng tương tự để dẫn tôi tin rằng điều này là có thể.

+2

Tôi không chắc chắn những gì bạn đang làm phù hợp với mô hình kiểu Haskell. Đó là một giá trị là một thể hiện của một loại lớp nên được chứng minh tĩnh. – millimoose

+4

Giá trị không thể là trường hợp của các lớp loại. Các kiểu là các thể hiện của các lớp kiểu. –

+4

Xem [mục FAQ này] (http://www.haskell.org/haskellwiki/FAQ#I.27m_making_an_RPG._Should_I_define_a_type_for_each_kind_of_monster.2C_and_a_type_class_for_them.3F). – ehird

Trả lời

8

Loại lớp học có thể là cách sai để truy cập tại đây. Thay vào đó, hãy xem xét sử dụng các loại dữ liệu đại số đơn giản, ví dụ:

data Item = Wood Int | Ash 

image Wood = "wood.png" 
image Ash = ... 

displayName Wood = "Wood" 
displayName Ash = "Ash" 

burn :: Item -> Maybe Item 
burn (Wood health) | health' <= 0 = Just Ash 
        | otherwise = Just (Wood health') 
    where health' = health - 100 
burn _ = Nothing -- Not flammable 

Nếu việc này quá khó để thêm mục mới, thay vào đó bạn có thể mã hóa các hoạt động trong chính loại dữ liệu đó.

data Item = Item { image :: String, displayName :: String, burn :: Maybe Item } 

ash :: Item 
ash = Item { image = "...", displayName = "Ash", burn :: Nothing } 

wood :: Int -> Item 
wood health = Item { image = "wood.png", displayName = "Wood", burn = Just burned } 
    where burned | health' <= 0 = ash 
       | otherwise = wood health' 
      health' = health - 100 

Tuy nhiên, điều này làm cho việc thêm chức năng mới trở nên khó khăn hơn. Vấn đề làm cả hai cùng một lúc được gọi là expression problem. Có một số nice lecture on Channel 9 by Dr. Ralf Lämmel nơi ông giải thích vấn đề này sâu hơn và thảo luận về nhiều giải pháp khác nhau, cũng đáng xem nếu bạn có thời gian.

Có cách tiếp cận để giải quyết, nhưng chúng phức tạp hơn nhiều so với hai thiết kế tôi đã minh họa, vì vậy tôi khuyên bạn nên sử dụng một trong số đó nếu nó phù hợp với nhu cầu của bạn và không lo lắng về vấn đề biểu hiện trừ khi bạn phải .

+0

Người đầu tiên có thể làm việc (mặc dù tôi không nghĩ rằng nó sẽ không có một Data.Data hack), nhưng nó sẽ làm cho nó khó khăn hơn để thêm nhiều mặt hàng (và có hơn 200 người trong số họ). Thứ hai sẽ không hoạt động vì một số mục không dễ cháy. Ý tôi là bạn có thể có 'Wood' rất dễ cháy và có thể được đưa vào hàng tồn kho và' Zombie' rất dễ cháy và có thể chiến đấu. Hai mục này có chung một đặc điểm chung, nhưng mỗi mục có một đặc điểm khác mà đặc điểm kia không có. – adrusi

+0

Đây là lý do tại sao tôi muốn sử dụng các loại lớp, trở ngại là tôi sẽ phải kiểm tra, ví dụ, cho dù mục mà người chơi tương tác là dễ cháy, có nghĩa là kiểm tra xem liệu có phải là thành viên của typeclass hay không . – adrusi

+0

@adrusi: Cho dù một cái gì đó là một thành viên của một lớp loại là một tài sản thời gian biên dịch, và do đó nó không thực sự phù hợp cho những gì bạn đang cố gắng để làm. Từ những gì bạn mô tả, có vẻ như phương pháp thứ hai sẽ hoạt động, mặc dù bạn có thể cần một số sửa đổi. Điều đó phụ thuộc vào những thuộc tính chung giữa các loại mặt hàng nhất định. Ví dụ: nếu bất kỳ mục nào có thể có bất kỳ kết hợp thuộc tính nào, bạn chỉ có thể có trường 'Có thể' cho mỗi thuộc tính, như trường tôi có cho các mục dễ cháy. Các mục không có thuộc tính đó sẽ chỉ có một 'Không có gì' ở đó. – hammar

5

Dưới đây là các vấn đề:

burn :: (Item b) => a -> b 

Điều này có nghĩa là giá trị kết quả của burn phải đa hình. Nó phải có thể điền bất kỳ lỗ nào cho bất kỳ phiên bản Item nào.

Bây giờ, nó khá rõ ràng bạn đang cố gắng để viết một cái gì đó như thế này (trong ngôn ngữ OO tưởng tượng với giao diện và subclassing):

Interface Item { 
    String getImage(); 
    String getDisplayName(); 
} 

Interface Flammable { 
    Item burn(); 
} 

Trong loại mã này, bạn đang nói rằng burn sẽ sản xuất một số mặt hàng, không có bất kỳ đảm bảo nào về loại của mặt hàng đó. Đây là sự khác biệt giữa "cho tất cả" và "có tồn tại". Những gì bạn muốn thể hiện trong mã Haskell là "có tồn tại", nhưng những gì bạn thực sự bày tỏ là "cho tất cả".

Bây giờ nếu bạn đang thực sự chắc chắn bạn muốn thực hiện chức năng "có", bạn có thể xem qua sử dụng Existential Types. Nhưng hãy cẩn thận. Nếu bạn đang lập kế hoạch viết mã như thế này:

if (foo instanceof Flammable) { 
    ... 
} 

Sau đó, bạn gần như chắc chắn làm sai và sẽ gặp nhiều đau đớn và đau đớn. Thay vào đó hãy xem xét các lựa chọn thay thế được đề xuất của hammar.

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