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

问题描述

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

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

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

common    = Rarity "C" 1
rare      = Rarity "R" 2
legendary = Rarity "L" 3
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
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

解决方法

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

大概你想要拥有稀有属性的物品。但是稀有字段应该是什么类型? CommonRareLegendary 都是 separate 类型,因此您不能只拥有 data Item = Item { ...,rarity :: Rarity }(更不用说所谓的额外稀有性了客户端程序添加的级别,如果这是在库中)。

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

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

{-# LANGUAGE GADTs #-}

data Item
  where Item :: Rarity r =>
          { ...,rarity :: r
          }
          -> Item

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

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

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

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

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

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

Int

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

我在 RPG 库中一般编写的库存系统可以使用 value 之类的类型来确保它考虑的所有项目一起使用相同稀有系统(避免出现来回答不合理的问题,例如一款游戏中的传奇物品比另一款游戏中的炫酷物品价值更高还是更低),但每个物品在该系统中都可以有自己的稀有度。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...