Should we go 'lensy' on accessing `System` ?
While we should continue to define System in the examples, I think we should slowly stop using System in type signatures but instead move towards requiring getters for the needed information instead.
This can be done incrementally, and will then help with later refactoring, so that less code will need to change. @balacij ?
I think System is commonly in type signatures only for grabbing the ChunkDB out of it. Maybe we should start with just passing through the ChunkDBs instead?
But I agree.
I think it also applied to ChunkDB. i.e. we should document "here is the information that I need" (via lenses / getters) instead of specifying an exact data type.
Now, a question is: what does going lensy on System look like?
Some of our fields are existentially quantified. So, lenses are a bit awkward. "Setting" should be fine, but I don't think we really need that. "Getting" is a bit awkward because we would "get" an existentially quantified type, which I don't think works too well.
data System where
SI :: (CommonIdea a, Idea a, ...) =>
{ _sys :: a
, ... }
getSys :: (CommonIdea a, Idea a) => System -> a
getSys SI{ _sys = s } = s
^ Invalid Haskell code.
That's why we need to unpack the inner System record in some areas:
https://github.com/JacquesCarette/Drasil/blob/7bb772731cdf3eb557fcccd19079e7feefcbf6ea/code/drasil-docLang/lib/Drasil/DocDecl.hs#L108-L111
^ On a similar train of thought, passing combinators doesn't work either (I initially thought this would work.):
getSys' :: (CommonIdea a, Idea a) => (a -> b) -> System -> b
getSys' f SI{ _sys = s } = f s
I'm not against retracting Systems constrained poly types to the ones we always use, and eventually seeing if we want to go back to the more "open" approach it currently allows (w.r.t. what types it accepts in its fields).
I forgot about forall -- Thank you @TOTBWF!
getSys' :: (forall a. (CommonIdea a, Idea a) => a -> r) -> System -> r
getSys' f (SI { _sys = s }) = f s
^ Works just fine now, but this is incompatible with lenses.
@TOTBWF was also had a solution to that:
data SomeCommonIdea where
SomeCommonIdea :: (CommonIdea a, Idea a) => a -> SomeCommonIdea
withSomeCommonIdea :: (forall a. (CommonIdea a, Idea a) => a -> r) -> SomeCommonIdea -> r
withSomeCommonIdea k (SomeCommonIdea a) = k a
sys :: Lens' System SomeCommonIdea
sys = lens (\SI{ _sys = _sys } -> SomeCommonIdea _sys) (\si -> withSomeCommonIdea $ \_sys -> si { _sys = _sys } )
So, we can get lenses!
That being said, this might be overkill and difficult to maintain. Is there really a benefit to keeping the polymorphic fields of System polymorphic?
We don't need lenses because we don't really ever 'set'. So for most things, only Getter is enough.
In general, any function that 'reads' information should be polymorphic. So reading from System should be polymorphic. However, creating a System doesn't need to be polymorphic.