jeudi 26 décembre 2019

Design pattern for subclass-like structure

My goal is to represent a set of types with a similar behaviour in a elegant and performant manner. To achieve this, I have created a solution that utilises a single type, followed by a set of functions that perform pattern matching.

My first question is: is there a way how to represent the same ideas using a single type-class and instead of having a constructor per each variation to have a type that implements said type-class?

Which of the two approaches below is: - a better recognised design pattern in Haskell? - more memory efficient? - more performant? - more elegant and why? - easier to use for consumers of the code?

Approach 1: Single type and pattern matching

Suppose there is a following structure:

data Aggregate a
  = Average    <some necessary state keeping>
  | Variance   <some necessary state keeping>
  | Quantile a <some necessary state keeping>

It's constructors are not public as that would expose the internal state keeping. Instead, a set of constructor functions exist:

newAverage :: Floating a
  => Aggregate a
newAverage = Average ...

newVariance :: Floating a
  => Aggregate a
newVariance = Variance ...

newQuantile :: Floating a
  => a                     -- ! important, a parameter to the function
  -> Aggregate a
newQuantile p = Quantile p ...

Once the object is created, we can perform two functions: put values into it, and once we are satisfied, we can get the current value:

get :: Floating a
  => Aggregate a
  -> Maybe a
get (Average <state>) = getAverage <state>
get (Variance <state>) = getVariance <state>
get (Quantile _ <state>) = getQuantile <state>

put :: Floating a
  => a
  -> Aggregate a
  -> Aggregate a
put newVal (Average <state>) = putAverage newVal <state>
put newVal (Variance <state>) = putVariance newVal <state>
put newVal (Quantile p <state>) = putQuantile newVal p <state>

Approach 2: Type-classes and instances

class Aggregate a where
  new :: a
  get :: Floating f => a f -> Maybe f
  put :: Floating f => 

data Average a = Average Word64 a
data Variance a ...

instance Aggregate Average where

instance Aggregate Variance where

instance Aggregate Quantile where

The obvious problem here is the fact that new is not parametric and thus Quantile can't be initialised with the p parameter. Adding a parameter to new is possible, but it would result in all other non-parametric constructors to ignore the value, which is not a good design.

Aucun commentaire:

Enregistrer un commentaire