如何在 Haskell 的通用“超类型”下拥有多种类型?

问题描述

[别介意这个问题 - 根据反馈,我意识到它措辞不当且过于含糊。我不会删除它,因为答案可以为他人服务。谢谢!]

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

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

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

data Type2 = Type2Level1 | Type2Level2 | Type2Level3 deriving (Eq,Enum) 

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

data GenType = Type1 | Type2

但这当然行不通。

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

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

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

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

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

看起来我在这里需要一个类但是 (1) 我不确定是不是这种情况和 (2) 当我尝试对 Entity 使用限定类型时,它不喜欢它(显然是这种不再支持限定):

data GenType a => Entity = Entity {
  entityId :: String,entityType :: a
} deriving (Eq,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 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 的构造函数名称是否可以作为其含义的人类可读文档。 (上面的示例中,名称表示字段类型,仅此而已,不是很好的人类可读文档。)第二个原因是,如果您实际上想要三种或更多类型的总和,因为嵌套的 Either变得笨拙。

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