2014-11-04 47 views
3

学习Haskell,我写了一个C++头文件的格式化程序。首先,我将所有班级成员解析成a-collection-of-class-members然后传递给格式化例程。为了表示类成员我有Haskell中的数据类型设计

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 

似乎并不成为一个选择。

我正在考虑的方案是:

  1. 存放在ClassMember没有对象实例本身,而是在相应的类型,这是由格式化程序需要定义的功能。这种方法打破了IMO的模块化,因为由[ClassMember]表示的解析结果需要知道所有的用法。

  2. 定义ClassMember作为存在类型,所以[ClassMember]已不再是一个问题。我怀疑这个设计是否足够严格,而且我需要在定义中指定所有约束条件,如data ClassMember = forall a . Formattable a => ClassMember a。另外,我更喜欢不使用扩展的解决方案。

是我正在做一个正确的方式来做到这一点在Haskell或有更好的办法吗?

+1

为什么你首先需要'Formattable'和'Prettifyable'类型的类吗?你是否想要格式化一些不是'ClassMember'的东西? – 2014-11-04 21:16:14

+0

@BenjaminHodgson:是的,这是问题所在。实际要格式化的东西是'data FormattableItem = FiClassMember ClassMember(Maybe SingleLineComment)| FiSingleLineComment SingleLineComment | FiComment评论| FiScopeModifier AccessModifier'。格式化规则有点花哨:“ClassMember”对象被其他“FormattableItem”对象分割成组,并且在组内也相互依赖。 – AdelNick 2014-11-05 09:20:32

回答

4

首先,考虑削减ADT一点。运算符重载和析构函数是特殊的方法,所以在处理CmMethod中的所有三个时可能更有意义;然后Method将有特殊的方式来分开它们。或者,保留全部三个CmMethod,CmOperatorOverloadCmDestructor,但让它们都包含相同的Method类型。

但是,当然,您可以只减少复杂性。

至于Show实例的具体示例:除了某些特殊情况外,您确实不想自己写这个实例。对于你的情况,这是更合理的做法是实例自动导出:

data ClassMember = CmTypedef Typedef 
       | CmMethod Method 
       | ... 
       | CmDestructor Destructor 
       deriving (Show) 

这会从您的自定义实例–得到不同的结果,因为你是错误的:显示一个包含结果也应提供有关构造函数的信息。

如果你不是在Show非常感兴趣,不过在谈论另一个类C,做更具体的ClassMember小号–好东西,那么你可能不应该定义在首位C!类型类的目的是表达适用于各种类型的数学概念。

+0

感谢您的意见。的确,'Show'在这里很混乱,我将它改为'Formattable'。还澄清了为什么我拥有所有这些构造函数 – AdelNick 2014-11-04 19:27:40

0

可能的解决方案是使用记录。 它可以使用没有扩展和保留灵活性。

还有一些样板代码,但你只需要输入一次。因此,如果您需要对您的ClassMember执行另一组操作,那么执行操作将非常简单快捷。

这里是你的特殊情况的一个例子(模板Haskell和Control.Lens使事情变得更容易,但不是强制性的):

{-# LANGUAGE TemplateHaskell #-} 

module Test.ClassMember 

import Control.Lens 

-- | The class member as initially defined. 
data ClassMember = 
     CmTypedef Typedef 
    | CmMethod Method 
    | CmOperatorOverload OperatorOverload 
    | CmVariable Variable 
    | CmFriendClass FriendClass 
    | CmDestructor Destructor 

-- | Some dummy definitions of the data types, so the code will compile. 
data Typedef = Typedef 
data Method = Method 
data OperatorOverload = OperatorOverload 
data Variable = Variable 
data FriendClass = FriendClass 
data Destructor = Destructor 

{-| 
A data type which defines one function per constructor. 
Note the type a, which means that for a given Hanlder "a" all functions 
must return "a" (as for a type class!). 
-} 
data Handler a = Handler 
    { 
     _handleType  :: Typedef -> a 
    , _handleMethod  :: Method -> a 
    , _handleOperator :: OperatorOverload -> a 
    , _handleVariable :: Variable -> a 
    , _handleFriendClass :: FriendClass -> a 
    , _handleDestructor :: Destructor -> a 
    } 

{-| 
Here I am using lenses. This is not mandatory at all, but makes life easier. 
This is also the reason of the TemplateHaskell language pragma above. 
-} 
makeLenses ''Handler 

{-| 
A function acting as a dispatcher (the boilerplate code!!!), telling which 
function of the handler must be used for a given constructor. 
-} 
handle :: Handler a -> ClassMember -> a 
handle handler member = 
    case member of 
     CmTypedef a   -> handler^.handleType $ a 
     CmMethod a   -> handler^.handleMethod $ a 
     CmOperatorOverload a -> handler^.handleOperator $ a 
     CmVariable a   -> handler^.handleVariable $ a 
     CmFriendClass a  -> handler^.handleFriendClass $ a 
     CmDestructor a)  -> handler^.handleDestructor $ a 

{-| 
A dummy format method. 
I kept things simple here, but you could define much more complicated 
functions. 

You could even define some generic functions separately and... you could define 
them with some extra arguments that you would only provide when building 
the Handler! An (dummy!) example is the way the destructor function is 
constructed. 
-} 
format :: Handler String 
format = Handler 
    (\x -> "type") 
    (\x -> "method") 
    (\x -> "operator") 
    (\x -> "variable") 
    (\x -> "Friend") 
    (destructorFunc $ (++) "format ") 

{-| 
A dummy function showcasing partial application. 
It has one more argument than handleDestructor. In practice you are free 
to add as many as you wish as long as it ends with the expected type 
(Destructor -> String). 
-} 
destructorFunc :: (String -> String) -> Destructor -> String 
destructorFunc f _ = f "destructor" 

{-| 
Construction of the pretty handler which illustrates the reason why 
using lens by keeping a nice and concise syntax. 

The "&" is the backward operator and ".~" is the set operator. 
All we do here is to change the functions of the handleType and the 
handleDestructor. 
-} 
pretty :: Handler String 
pretty = format & handleType  .~ (\x -> "Pretty type") 
       & handleDestructor .~ (destructorFunc ((++) "Pretty ")) 

现在我们可以运行一些测试:

test1 = handle format (CmDestructor Destructor) 
> "format destructor" 

test2 = handle pretty (CmDestructor Destructor) 
> "Pretty destructor"