Manage values ​​inside structurally similar types in Haskell

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 would like to have a set of functions that work in the parameter list, for example, to add and remove elements from the list (returning a new Foo or Bar with a different parameter list, depending on the situation).

As far as I can tell, even if I create a typeclass and create instances for each type, I will need to define all these functions each time, that is:

 -- 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 

It feels tiring - far beyond the extent that I begin to think that I am doing something wrong. If you cannot match the template, regardless of the constructor (?), To extract the value, how can you reduce the template?

To refute a possible line of argument: yes, I know that I could just have one set of functions ( addParam , etc.) that would explicitly display each constructor and pattern matching with parameters - but since I built it pretty modularly (modules Foo and Bar pretty self-sufficient), and I am prototyping a system where there will be dozens of these types, a detailed centralized list of type constructors seems ... wrong.

Is it possible that my approach is simply wrong and that it is not possible to structure the type hierarchy as best as possible, but since I cannot have one data type somewhere and add a new constructor for the type in each of these modules (?). I am puzzled by how to get a nice "plugin" without reviewing the simple functions of the utility every time. Any and all good offers were accepted with pleasure.

+4
source share
3 answers

You may have standard implementations of functions in a type class, for example.

 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 looks suspicious.

+4
source

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

If they really should be structurally similar, but you encoded it in such a way that no code can be shared, then this is a suspicious pattern.

+3
source

It is hard to say from your example what would be the right way.

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

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

Sort of

 $(superDuper "Foo") 

can generate 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] 

This will at least get rid of the boiler plate coding.

+2
source

All Articles