如何在 Haskell 中的泛型 "super type" 下拥有多个类型?

How to have multiple types under a generic "super type" in Haskell?

[不要管这个问题 - 根据反馈,我意识到它措辞不佳且过于模糊。我不删除它,因为答案可以为其他人服务。谢谢!]

我正在寻找一种类型结构,它允许我将两个相似的有序数据类型放在一个通用类型下。

例如,假设我有以下有序类型:

data Type1 = Type1Level1 | Type1Level2 | Type1Level3 | Type1Level4 deriving (Eq, Ord, Show, Read, Bounded, Enum) 

data Type2 = Type2Level1 | Type2Level2 | Type2Level3 deriving (Eq, Ord, Show, Read, Bounded, Enum) 

本着精神,我想要一个通用的总体类型,例如:

data GenType = Type1 | Type2

但这当然行不通。

我碰巧拥有与其中一种类型相关的实体:

data Entity = Entity {
  entityId :: String,
  entityType :: -- here I want one of the TypeXLevelY (anything "under GenType")
 } deriving (Eq, Ord, Read, Show)

但我还希望这些实体具有特定字段,具体取决于它们是 type1 还是 type2(这是否意味着我必须制作 2 种不同的“实体”数据类型?)。例如,type1 的实体应该有一个字段“input”,但 type2 的实体没有。

此外,type1 和 type2 应该能够共享顺序和几个共同的函数,这样这样的事情才有意义:

testSuccType :: GenType -> GenType -> Bool
testSuccType type1 type2 = (type2 == succ type1)

看起来我在这里需要一个 class 但是 (1) 我不确定情况是否如此以及 (2) 当我尝试为实体使用限定类型时,它不喜欢它(显然不再支持这种资格):

data GenType a => Entity = Entity {
  entityId :: String,
  entityType :: a
} deriving (Eq, Ord, Read, Show)

我希望这已经足够清楚了。 非常感谢您的帮助。

我想我没有完全理解这个问题,但也许我可以给出一些提示。

您要查找的数据类型定义可能是:

data GenType = GT1 Type1 | GT2 Type2

或者,有些惯用,您可以将构造函数名称与它们的(唯一)字段的类型双关:

data GenType = Type1 Type1 | Type2 Type2

然后你可以像这样实现你的共享函数:

getNextLevelGenType (GT1 t1) = GT1 (getNextLevelType1 t1)
getNextLevelGenType (GT2 t2) = GT2 (getNextLevelType2 t2)

-- OR

getNextLevelGenType (Type1 t1) = Type1 (getNextLevelType1 t1)
getNextLevelGenType (Type2 t2) = Type2 (getNextLevelType2 t2)

有时通过类型 class 共享方法名称是有意义的,但这是否有意义在很大程度上取决于问题中未共享的细节。如果确实有意义,它可能看起来像这样:

class Leveled a where getNextLevel :: a -> a
instance Leveled Type1 where getNextLevel = succ
instance Leveled Type2 where getNextLevel = succ
instance Leveled GenType where
    getNextLevel (Type1 t1) = Type1 (getNextLevel t1)
    getNextLevel (Type2 t2) = Type2 (getNextLevel t2)

如果你走那条路,有一个通用的、参数化的总和类型可能值得考虑作为 GenType 的替代,名为 Either。避免考虑它的主要原因是 GenType 的构造函数名称是否可以作为其含义的人类可读文档。 (上面的例子中,名称只表示字段类型,不是很好的人类可读文档。)第二个原因是如果你真的想要三种或更多类型的总和,因为嵌套 Eithers变得笨拙。

instance (Leveled a, Leveled b) => Leveled (Either a b) where
    getNextLevel (Left a) = Left (getNextLevel a)
    getNextLevel (Right b) = Right (getNextLevel b)

    -- OR, with Data.Bifunctor imported,

    getNextLevel = bimap getNextLevel getNextLevel