我有一个实体列表,比方说[Entity]
。每个Entity
具有不同的功能,例如一些可以绘制到屏幕,而其他可以发出声音。所有这些都可以打印出来以获得调试信息。
鉴于此,我们可能会有三个类型类:Show,Draw&声音。一个实体可能是Show和Draw的实例,而另一个实体可能是Show和Sound。
我试图找到Entity
的类型,给定这些约束,并且考虑到我想按功能遍历列表,例如:找到可以显示的所有实体,或者所有能播放声音的实体。
到目前为止,我无法用haskell的类型系统来表达这一点,唯一的方法似乎是使用运行时检查,基本上是实现我自己的约束系统。
有什么想法吗?
答案 0 :(得分:5)
如果您要以不同于构造和使用Entity
的方式使用类型类,我建议使用这些类型类进行建模 - 否则会浪费时间和代码行。请参阅the existential typeclass (anti-)pattern。
您描述Entity
的方式是“功能”的集合。包含要素字段的记录是对此进行建模的常用且简洁的方法。对于可选功能,请使用Maybe
:
data Entity = Entity {
image :: Maybe Image,
sound :: Maybe Sound,
debug :: String
}
instance Show Entity where show = debug
(我使用的是语义上有意义的名称,例如Image
和Sound
,如果你想对这些名称进行必要的考虑,你可以替换IO ()
。)
请记住,您可以在这里使用HOF,因此功能特性是可以接受的:
converse :: Maybe (Question -> Answer)
通常用于此问题的模式是,不是使用Entity { ... }
创建实体,而是让它们专门化为默认值:
defaultEntity :: Entity
defaultEntity = Entity {
image = Nothing,
sound = Nothing,
debug = "<Entity>"
}
然后定义如下的实体:
invisibleDog :: Entity
invisibleDog = defaultEntity {
debug = "<Invisible Dog>",
sound = Just (Sound.resource "woof.wav")
}
现在,当您添加新功能时,您只需要更新defaultEntity
,所有其他实体都将继承默认值。
答案 1 :(得分:3)
尝试在类型级别表达Entity
之间的差异,您将没有太多运气。至少,如果你想将它们存储在同质[]
中,那就不是了。您立即受到限制,Entity
的所有值必须按类型无法区分。
我认为类型类或存在性类型不是必需的。我建议如下:
data Trait = Showable String | Drawable Image | Playable Sound
根据您计划绘制图像/播放声音的方式定义Image
和Sound
。然后Entity
是这些特征的简单可组合集合:
newtype Entity = Entity (Set Trait)
您可以轻松地为Monoid
提供Entity
个实例,以允许Trait
的组合。然后,您可以“按功能遍历列表”,如下所示:
showables :: [Entity] -> [Entity]
showables = filter $ \(Entity es) -> any isShowable (Set.toList es)
where isShowable (Showable _) = True
isShowable _ = False
具有Drawable
和Playable
的相似功能。
此解决方案是否满足您的所有标准?
编辑: luqui指出了这个模型的一些缺陷。可以使用手动Eq
和Ord
个实例来避免这些情况。
instance Eq Trait where
Showable _ == Showable _ = True
Drawable _ == Drawable _ = True
Playable _ == Playable _ = True
_ == _ = False
instance Ord Trait where
Showable _ <= _ = True
_ <= Playable _ = True
_ <= _ = False
现在Trait
中无法复制Set
并提供可行的Ord
实例。这有点拉伸,可能不适合您的用例。