Types strengthening
In a previous log, I have introduced librarian, as an example of Type-Driven Development.
For a reminder, we had "complex" types:
-- GADTs
data GroupingBucket :: Type -> Type where
Daily :: GroupingBucket UTCTime
Weekly :: GroupingBucket UTCTime
Monthly :: GroupingBucket UTCTime
-- Existential
data Filtering
= AllF
| AndF Filtering Filtering
| OrF Filtering Filtering
| forall a. Ord a => GtF (Source a) (Source a)
| forall a. Ord a => LtF (Source a) (Source a)
There are complex enough to don't be supported by the GHC Generic
derivation,
consequently, we cannot derive FromDhall
.
Note: I have tried to implement them myself, don't do that, even if you like pain
The simplest solution is to use an intermediate data type which would be simpler and bounded to parsing (it is a Data Transfer Object (DTO)):
data Filtering
= All
| And Filtering Filtering
| Or Filtering Filtering
| GtTemporal SourceTemporal SourceTemporal
| LtTemporal SourceTemporal SourceTemporal
deriving stock (Eq, Show)
data GroupSelectionTemporal
= AfterTemporal Int SortingOrder SourceTemporal
| BeforeTemporal Int SortingOrder SourceTemporal
deriving stock (Eq, Show, Generic)
deriving anyclass
data SourceTemporal
= SourceDate SourceDate
| SourceTime TimeSpec
deriving stock (Eq, Show, Generic)
deriving anyclass
I'm forced to specialize data types in order to keep type-safety.
There is another issue, since Filtering
is recursive, FromDhall
derivation
cannot be properly done, consequently, we have to use fixpoints,
at type-level it is defined in Haskell as follows:
newtype Fix f = Fix { unFix :: f (Fix f) }
The issue is that Filtering
does not have parameter, hopefully there is a
TemplateHaskell
way to derive it:
TH.makeBaseFunctor ''Filtering
Which will generate something like that:
data FilteringF a
= All
| And a a
| Or a a
| GtTemporal SourceTemporal SourceTemporal
| LtTemporal SourceTemporal SourceTemporal
deriving stock (Eq, Show)
Which allows us to derive FromDhall
and to use it for our Rule
:
deriving stock
deriving anyclass
data Rule = Rule
{ name :: RuleName,
}
deriving stock (Generic)
deriving anyclass
The final part consists in convert the above types (Dhall-compatible, source S
),
to the library types (target, T
):
convertGrouping =
\case
S.FileGroup -> T.FileGroup
S.GroupTemporally source bucket selection ->
T.Group
{ groupSource = convertSourceTemporal source,
groupBucket = convertGroupingBucketTemporal bucket,
groupSelection = convertGroupSelectionTemporal selection
}
convertAction =
\case
S.Move {..} -> T.Move {inputPattern = inputPattern, newName = newName}
S.Copy {..} -> T.Copy {inputPattern = inputPattern, newName = newName}
S.Remove {..} -> T.Remove {inputPattern = inputPattern}
convertSourceTemporal =
\case
S.SourceDate x -> T.SourceDate $ convertSourceDate x
S.SourceTime x -> T.SourceTime $ convertTimeSpec x
convertGroupingBucketTemporal =
\case
S.Daily -> T.Daily
S.Weekly -> T.Weekly
S.Monthly -> T.Monthly