Extending structures in Haskell

202 views Asked by At

I want to implement some algorithms that have a common set of parameters (in practice this is a high number, which is why I am not passing them separately into the functions):

data Parameters = Parameters {
   _p1 :: A,
   ...
}

But each one of them has -aside from this common set- a set of parameters that only they know how to use:

data AlgorithmAParameters = AlgorithmAParameters {
    _commonParameters :: Parameters,
    _myp1 :: B
}

The problem here is how to write idiomatic code. I am currently using lenses, so then I can define

p1 :: Lens' AlgorithmAParameters A
p1 = commonParameters . Common.p1

And this lets me access everything the same way I would if I were using just Parameters. The problem is that I have to do this for every algorithm that keeps its own set of parameters, and I have to be careful to import these separately, among other things.

I could go further and use type classes

class Parameters p where
    p1 :: Lens' p A
    ...

And then implement separately

class AlgorithmAParameters p where
    p1 :: Lens' p A
    myp1 :: Lens' p B

Along with the AlgorithmAParameters p => AlgorithmParameters p instance. However, this has the same kind of problem (repeated code) and ultimately leads to code that is just as misleading as the first option (plus the whole Lens' in the type class is not very informative).

Is there an easier way to solve this?.

2

There are 2 answers

0
frasertweedale On BEST ANSWER

The classy lenses/optics technique is useful here.

data CommonParameters = CommonParameters
  { _p1 :: A
  }
makeClassy ''CommonParameters

The makeClassy Template Haskell directive will result in the following class and instance:

class HasCommonParameters a where
  commonParameters :: Lens' a CommonParameters
  p1 :: Lens' a A
  p1 = ... -- default implementation

instance HasCommonParameters CommonParameters where
  commonParameters = id

Then for AlgorithmParameters

data AlgorithmParameters = AlgorithmParameters
  { _algCommonParameters :: CommonParameters
  , _myp1 :: B
  }
makeClassy ''AlgorithmParameters

Again makeClassy does its thing:

class HasAlgorithmParameters a where
  algorithmParameters :: Lens' a AlgorithmParameters
  algCommonParameters :: Lens' a CommonParameters
  algCommonParameters = ... -- default implementation
  myp1 :: Lens' a B
  myp1 = ... -- default implementation

instance HasAlgorithmParameters AlgorithmParameters where
  algorithmParameters = id

Now, so that you can use the CommonParameters optics with the AlgorithmParameters type, define the following instance:

instance HasCommonParameters AlgorithmParameters where
  commonParameters = algCommonParameters
0
John F. Miller On

What you are looking for is makeClassy from Control.Lens.TH. Read the documentation about its assumptions on field names. (If you cannot change your field names to match see friends makeClassyFor and makeClassy_)

The idea here is that the Template creates a class of things that have your commonParameters and adds your data structure as an instance of that class. When many data structures have the same fields they will all be part of the same class. You can then use that class in your lens accessors.

As a programming note, I tend to make a generic structure with just the commonParameters and a reasonable name so I can refer to the class created by TH using the HasFoo convention.