总和类型 vs. 类型类 vs. 记录

the*_*heo 5 haskell typeclass algebraic-data-types

以下三种方法中哪一种最适合为 RPG 中的物品实现某种稀有性?

sum 类型的方法“感觉”是正确的,因为稀有性似乎是一个“封闭集”(或不可变的?idk,如果这是正确的词)。但是,如果这是在图书馆中定义的,那么我将无法添加更多看起来很奇怪的稀有物品。

data Rarity = Rarity { symbol :: String, value :: Int }

common    = Rarity "C" 1
rare      = Rarity "R" 2
legendary = Rarity "L" 3
Run Code Online (Sandbox Code Playgroud)
data Rarity = Common | Rare | Legendary

symbol :: Rarity -> String
symbol Common    = "C"
symbol Rare      = "R"
symbol Legendary = "L"

value :: Rarity -> Int
value Common    = 1
value Rare      = 2
value Legendary = 3
Run Code Online (Sandbox Code Playgroud)
class Rarity r where
  symbol :: r -> String
  value :: r -> Int

data Common = Common
instance Rarity Common where
  symbol _ = "C"
  value _ = 1

data Rare = Rare
instance Rarity Rare where
  symbol _ = "R"
  value _ = 2

data Legendary = Legendary
instance Rarity Legendary where
  symbol _ = "L"
  value _ = 3
Run Code Online (Sandbox Code Playgroud)

Ben*_*Ben 6

您展示的类型类方法在实践中不是很符合人体工程学。

大概您想要具有稀有属性的物品。但是稀有度字段应该是什么类型?Common, Rare, 和Legendary都是独立的类型,所以你不能只拥有data Item = Item { ..., rarity :: Rarity }(更不用说客户端程序添加的假设的额外稀有级别,如果它在库中)。

您可以拥有data Item r = Item { ..., rarity :: r },但现在项目列表(或任何其他通用集合)的类型必须指定其中所有项目具有的单一稀有级别(例如[Item Common])。这不是您希望在实践中使用稀有度的方式(因为例如玩家的库存可以包含不同稀有度的物品!)。

您可以通过使用存在类型来解决这个问题:

{-# LANGUAGE GADTs #-}

data Item
  where Item :: Rarity r =>
          { ...
          , rarity :: r
          }
          -> Item
Run Code Online (Sandbox Code Playgroud)

但是现在这基本上与您的第一个建议(使用data Rarity = Rarity { symbol :: String, value :: Int })同构,只是使用起来更复杂。由于您不能对限制在Rarity类型类中的未知类型的值执行任何Rarity操作,而不是调用方法,而所有这些都是为您提供 aString和 an Int,因此您可能刚刚使用了 a 的记录String和一个Int开始,并保存所有扩展和样板。

所以现在我们回到前两个版本:记录或总和类型。

记录版本的一个潜在优势是您(或任何客户端代码)可以提出任意的新稀有级别。一个很大的潜在缺点是您(或任何客户端代码)可以提出任意的新稀有度级别,而无法保证与其他项目中使用的稀有度级别保持一致。

这是否是一个功能或一个问题实际上取决于你打算如何处理String,并Int构成一个罕见的水平。如果你真的可以在你的 RPG 库中编写引擎代码来处理它们之间的任何属性完全不可知,那么记录就是正确的方法。但我的直觉是 RPG 稀有度,你不会那样做。您将依赖于排序,您将依赖于每次看到它时String对应于相同 Int值的给定稀有代码,等等。

在编写个人 RPG 时,我会选择静态总和类型。它更符合我对稀有度的概念(如你所说,在一个游戏中,它通常应该是一个封闭的集合,而不是可任意扩展的)。

为了编写 RPG 库,我将使用类型类,但与您使用的不同。

class Rarity r where
  symbol :: r -> String
  value :: r -> Int

data GameOneRarity = Common | Rare | Legendary

instance Rarity GameOneRarity
  where symbol Common = "C"
        symbol Rare = "R"
        symbol Legendary = "L"

        value Common = 1
        value Rare = 2
        value Legendary = 3


data GameTwoRarity = Boring | Cool | Awesome

instance Rarity GameTwoRarity
  where symbol Boring = "B"
        symbol Cool = "C"
        symbol Awesome = "A"

        value Boring = 100
        value Cool = 1000
        value Awesome = 10000000
Run Code Online (Sandbox Code Playgroud)

我没有为每个新的稀有度级别设置单独的类型,我为每个稀有度方案设置一个单独的类型。这个想法是让每个游戏定义自己的稀有类型。一组单独的稀有度是不可扩展的,但库代码可以处理游戏设计者想要的任何稀有度级别的任意方案(只要他们可以实现symbolvalue为他们实现)。

我在 RPG 库中一般编写的库存系统可以使用像这样的类型[Item r]来确保它考虑的所有物品一起使用相同的稀有度系统(避免不得不回答不合理的问题的可能性,例如一个游戏中的传奇物品是否是或多或少比来自不同游戏的酷物品更有价值)但每个物品在该系统中都有自己的稀有度。