Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data type design in Haskell

Tags:

haskell

Learning Haskell, I write a formatter of C++ header files. First, I parse all class members into a-collection-of-class-members which is then passed to the formatting routine. To represent class members I have

data ClassMember = CmTypedef Typedef |
                   CmMethod Method |
                   CmOperatorOverload OperatorOverload |
                   CmVariable Variable |
                   CmFriendClass FriendClass |
                   CmDestructor Destructor

(I need to classify the class members this way because of some peculiarities of the formatting style.)

The problem that annoys me is that to "drag" any function defined for the class member types to the ClassMember level, I have to write a lot of redundant code. For example,

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

On the other hand, I would definitely like to have a list of ClassMember objects (at least, I think so), hence defining it as

data ClassMember a = ClassMember a

instance Formattable ClassMember a
    format (ClassMember a) = format a

doesn't seem to be an option.

The alternatives I'm considering are:

  1. Store in ClassMember not object instances themselves, but functions defined on the corresponding types, which are needed by the formatting routine. This approach breaks the modularity, IMO, as the parsing results, represented by [ClassMember], need to be aware of all their usages.

  2. Define ClassMember as an existential type, so [ClassMember] is no longer a problem. I doubt whether this design is strict enough and, again, I need to specify all constraints in the definition, like data ClassMember = forall a . Formattable a => ClassMember a. Also, I would prefer a solution without using extensions.

Is what I'm doing a proper way to do it in Haskell or there is a better way?

like image 637
AdelNick Avatar asked Jan 18 '26 02:01

AdelNick


1 Answers

First, consider trimming down that ADT a bit. Operator overloads and destructors are special kinds of methods, so it might make more sense to treat all three in CmMethod; Method will then have special ways to separate them. Alternatively, keep all three CmMethod, CmOperatorOverload, and CmDestructor, but let them all contain the same Method type.

But of course, you can reduce the complexity only so much.

As for the specific example of a Show instance: you really don't want to write that yourself except in some special cases. For your case, it's much more reasonable to have the instance derived automatically:

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

This will give different results from your custom instance – because yours is wrong: showing a contained result should also give information about the constructor.

If you're not really interested in Show but talking about another class C that does something more specific to ClassMembers – well, then you probably shouldn't have defined C in the first place! The purpose of type classes is to express mathematical concepts that hold for a great variety of types.

like image 189
leftaroundabout Avatar answered Jan 19 '26 20:01

leftaroundabout