Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Operate on values within 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'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.

like image 416
Bruce Williams Avatar asked Jan 24 '23 02:01

Bruce Williams


1 Answers

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.

like image 52
Alexey Romanov Avatar answered Mar 01 '23 23:03

Alexey Romanov