Drasil icon indicating copy to clipboard operation
Drasil copied to clipboard

Should we go 'lensy' on accessing `System` ?

Open JacquesCarette opened this issue 8 months ago • 2 comments

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 ?

JacquesCarette avatar May 08 '25 15:05 JacquesCarette

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.

balacij avatar May 08 '25 18:05 balacij

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.

JacquesCarette avatar May 09 '25 20:05 JacquesCarette

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).

balacij avatar Jul 21 '25 17:07 balacij

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?

balacij avatar Jul 21 '25 17:07 balacij

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.

JacquesCarette avatar Jul 21 '25 20:07 JacquesCarette