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 newtype
s, 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.