Reputation: 1312
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?.
Upvotes: 2
Views: 188
Reputation: 5674
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
Upvotes: 1
Reputation: 27225
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.
Upvotes: 1