2

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

5
  • 2
    Probably not the answer you're looking for, but my opinion/experience here is that you need to abstract. A function that depends on 5 parameters is already conceptually too complicated -- what does it really depend on, in your mind? Are there collections of these parameters that form cohesive units? Perhaps the types are too simplistic (as in C where you pass an int and a pointer when you really mean an array). Perhaps the algorithm is not general/polymorphic enough, so where you think you need to be consulting settings, in fact a helper function needs to be factored into a parameter Commented Sep 5, 2017 at 21:13
  • Anyway I wrote this controversial post about a similar situation lukepalmer.wordpress.com/2011/05/20/the-whole-program-fallacy Commented Sep 5, 2017 at 21:16
  • "Is there an easier way to solve this?" - what is hard about either of your two solutions? Based on this very abstract explanation, I would hazard a guess that you want "row polymorphism" (or "extensible records") for which there are dozens of packages on Hackage. However, this type of solution often comes with its own caveats (e.g. worse type inference, requiring complex type-level machinery, requiring TH in user code). Commented Sep 5, 2017 at 22:09
  • Why not just use both access functions: _p1 $ _common myparams or similar? If that's not sufficient, ask yourself: exactly what do you want to do with your common parameters? In what ways do you need to treat your variant parameter sets uniformly? Commented Sep 6, 2017 at 3:55
  • As you are using lens you could use either mkClass or mkField Commented Sep 6, 2017 at 6:15

2 Answers 2

1

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
Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.