I have a bunch of type which holds time series data.
Like, a list of journal with dates ,a bank statement with list of transactions.
I want to make a class to annotate these type slice-able like in Python slice [:]
that would create a nightmare to implement TsList to all the types because:
there are lots of filter ... txns in common . Is there any way to factor out this common logic ?
data BoundDate = Include Date
| Exclude Date
class TsList a where
subByRange :: a -> Maybe BoundDate -> Maybe BoundDate -> a
--- with Statement type
instance TsList Statement where
subByRange s@(Statement txns) Nothing Nothing = s
subByRange s@(Statement txns) Nothing (Just ed) =
case ed of
Include d -> Statement $ filter ( x -> x <= d ) txns
Exclude d -> Statement $ filter ( x -> x < d ) txns
subByRange s@(Statement txns) (Just sd) Nothing =
case sd of
Include d -> Statement $ filter ( x -> x => d ) txns
Exclude d -> Statement $ filter ( x -> x > d ) txns
subByRange s@(Statement txns) (Just sd) (Just ed) =
Statement $ subByRange _s Nothing (Just ed)
where
_s = Statement $ subByRange s (Just sd) Nothing
--- now with Journal type
instance TsList Journal where
subByRange s@(Journal txns) Nothing Nothing = s
subByRange s@(Journal txns) Nothing (Just ed) =
case ed of
Include d -> Journal $ filter ( x -> x <= d ) txns
Exclude d -> Journal $ filter ( x -> x < d ) txns
subByRange s@(Statement txns) (Just sd) Nothing =
case sd of
Include d -> Journal $ filter ( x -> x => d ) txns
Exclude d -> Journal $ filter ( x -> x > d ) txns
subByRange s@(Statement txns) (Just sd) (Just ed) =
Journal $ subByRange _s Nothing (Just ed)
where
_s = Journal $ subByRange s (Just sd) Nothing
CodePudding user response:
As @FyodorSoikin mentioned in the comments, perhaps the easiest way to do this is to use the GHC language extension DerivingVia. To do this, add the following declaration to the top of your file:
{-# LANGUAGE DerivingVia #-}
Now, assuming that your types are defined as newtypes, GHC will be able to automatically derive one instance from the other if you tell it so:
newtype Statement = Statement [Int] -- or something like this
instance TsList Statement where
-- ...as in the question...
newtype Journal = Journal [Int]
deriving (TsList) via Statement
(Note that this only works if both Statement and Journal are newtype wrappers around the same type.)
CodePudding user response:
One option is to implement these functions over just plain old lists and then the class instances are just newtype wrapper/unwrappers.
For instance, you can define:
subByRangeList :: [Date] -> Maybe BoundDate -> Maybe BoundDate -> [Date]
subByRangeList s Nothing Nothing = s
subByRangeList s Nothing (Just ed) =
case ed of
Include d -> filter ( x -> x <= d ) s
Exclude d -> filter ( x -> x < d ) s
subByRangeList s (Just sd) Nothing =
case sd of
Include d -> filter ( x -> x >= d ) s
Exclude d -> filter ( x -> x > d ) s
subByRangeList s (Just sd) (Just ed) =
subByRangeList _s Nothing (Just ed)
where
_s = subByRangeList s (Just sd) Nothing
And then your instances are simply:
instance TsList Statement where
subByRange (Statement txns) = Statement $ subByRangeList txns
instance TsList Journal where
subByRange (Journal txns) = Journal $ subByRangeList txns
One potential annoyance of this is if you have a bunch of class methods in TsList (or a bunch of instances), and you don't want to even have to define each one the same way with the newtype wrapping/unwrapping. You can make use of DefaultSignatures and the Coercible type class to write:
class TsList a where
subByRange :: a -> Maybe BoundDate -> Maybe BoundDate -> a
default subByRange :: (Coercible a [Date], Coercible [Date] a) => a -> Maybe BoundDate -> Maybe BoundDate -> a
subByRange s x y = coerce $ subByRangeList (coerce s) x y
instance TsList Statement
instance TsList Journal
Because Statement and Journal are simply newtype wrappers around [Date], they will be able to use the default version of subByRange, so all you need to do is declare that they're instances.
