views:

143

answers:

3

Excuse me for my extremely limited Haskell-fu.

I have a series of data types, defined in different modules, that are structured the same way:

-- in module Foo
data Foo = Foo [Param]

-- in module Bar
data Bar = Bar [Param]

-- * many more elsewhere

I'd like to have a set of functions that operate on the list of params, eg to add and remove elements from the list (returning a new Foo or Bar with a different list of params, as appropriate).

As far as I can tell, even if I create a typeclass and create instances for each type, I'd need to define all of these functions each time, ie:

-- in some imported module

class Parameterized a where
    addParam :: a -> Param -> a
    -- ... other functions

-- in module Foo

instance Parameterization Foo where
    addParam (Foo params) param = Foo (param:params)
    -- ... other functions

-- in module Bar

instance Parameterization Bar where
    -- this looks familiar...
    addParam (Bar params) param = Bar (param:params)
    -- ... other functions

This feels tedious -- far past the degree where I start thinking I'm doing something wrong. If you can't pattern match regardless of constructor (?) to extract a value, how can boilerplate like this be reduced?

To rebut a possible line of argument: yes, I know I could simply have one set of functions (addParam, etc), that would explicitly list each constructor and pattern match put the params -- but as I'm building this fairly modularly (the Foo and Bar modules are pretty self-contained), and I'm prototyping a system where there will be dozens of these types, a verbose centralized listing of type constructors seems... wrong.

It's quite possible (probable?) that my approach is simply flawed and that this isn't anywhere near the right way to structure the type hierarchy anyhow -- but as I can't have a single data type somewhere and add a new constructor for the type in each of these modules (?) I'm stumped at how to get a nice "plugin" feel without having to redefine simple utility functions each time. Any and all kind suggestions gladly accepted.

+2  A: 

It's hard to tell from your example what would be the right way.

Why do you need both Foo and Bar if they are structurally similar? Can't you just go with Foo?

If, for some reason I can't see, you need both Foo and Bar you'll have to use a type class but you can clean up the code using Template Haskell.

Something like

$(superDuper "Foo")

could generate the code

data Foo = Foo [Param]

instance Parameterization Foo where
    addParam (Foo params) param = Foo (param:params)

where

superDuper :: String -> Q [Dec]
superDuper n =
  let name = mkName n
      dataD = DataD [] name [] [NormalC name [] ] -- correct constructor here
      instD = InstanceD [] ... -- add code here
  return [dataD, instD]

That would at least get rid of the boiler plate coding.

finnsson
I've wanted to check out Template Haskell for awhile now; thanks for this example.
Bruce Williams
+3  A: 

You can have default implementations of functions in a typeclass, e.g.

class Parameterized a where
    params :: a -> [Param]
    fromParams :: [Param] -> a
    addParam :: a -> Param -> a
    addParam x par = fromParams $ par : params x
    -- ... other functions

instance Parameterized Foo where
    params (Foo pars) = pars
    fromParams = Foo

However, your design does look suspicious.

Alexey Romanov
+3  A: 

You could use a newtype for Foo and Bar, and then deriving the implementation based on the underlying structure (in this case, lists) automatically with -XGenerializedNewtypeDeriving.

If they're really supposed to be structurally similar, yet you've coded it in a way that no code can be shared, then that is a suspicious pattern.

Don Stewart