学习Haskell,我写了一个C头文件的格式化程序 . 首先,我将所有类成员解析为a-collection-of-class-members,然后将其传递给格式化例程 . 代表我有的 class 成员
data ClassMember = CmTypedef Typedef |
CmMethod Method |
CmOperatorOverload OperatorOverload |
CmVariable Variable |
CmFriendClass FriendClass |
CmDestructor Destructor
(由于格式化风格的一些特殊性,我需要以这种方式对类成员进行分类 . )
让我烦恼的问题是,为 ClassMember
级别的类成员类型定义的任何函数,我必须编写大量冗余代码 . 例如,
instance Formattable ClassMember where
format (CmTypedef td) = format td
format (CmMethod m) = format m
format (CmOperatorOverload oo) = format oo
format (CmVariable v) = format v
format (CmFriendClass fc) = format fc
format (CmDestructor d) = format d
instance Prettifyable ClassMember where
-- same story here
另一方面,我肯定希望有一个 ClassMember
对象的列表(至少,我认为是这样),因此将其定义为
data ClassMember a = ClassMember a
instance Formattable ClassMember a
format (ClassMember a) = format a
似乎不是一个选择 .
我正在考虑的替代方案是:
-
存储在
ClassMember
中不是对象实例本身,而是在相应类型上定义的函数,这些函数是格式化例程所需的 . 这种方法打破了模块化,IMO,因为[ClassMember]
表示的解析结果需要知道它们的所有用法 . -
将
ClassMember
定义为存在类型,因此[ClassMember]
不再是问题 . 我怀疑这个设计是否足够严格,同样,我需要在定义中指定所有约束,如data ClassMember = forall a . Formattable a => ClassMember a
. 此外,我更喜欢不使用扩展的解决方案 .
我正在以正确的方式在Haskell中做到这一点还是有更好的方法?
2 回答
首先,考虑稍微削减ADT . 运算符重载和析构函数是一种特殊的方法,因此在
CmMethod
中处理所有三个方法可能更有意义;Method
将有特殊的方法来分开它们 . 或者,保留所有三个CmMethod
,CmOperatorOverload
和CmDestructor
,但让它们都包含相同的Method
类型 .但是,当然,你可以减少这么多的复杂性 .
至于
Show
实例的具体示例:您真的不需要自动导出实例更合理:这将给您的自定义实例提供不同的结果 - 因为您的错误:显示包含的结果还应该提供有关构造函数的信息 .
如果你对
Show
不是真的感兴趣,但是在讨论另一个类C
,它会对ClassMember
做更具体的事情 - 那么你可能不应该首先定义C
!类型类的目的是表达适用于各种类型的数学概念 .一种可能的解决方案是使用记录 . 它可以在没有扩展的情况下使用并保持灵活性 .
仍然有一些样板代码,但您只需要输入一次 . 因此,如果您需要在ClassMember上执行另一组操作,则可以非常轻松快速地执行此操作 .
以下是您的特定情况的示例(模板Haskell和Control.Lens使事情变得更容易但不是强制性的):
现在我们可以运行一些测试: