jcpetruzza / barbies

BSD 3-Clause "New" or "Revised" License
92 stars 15 forks source link

Question: Combining bi-functors/nesting and with covering/stripping #55

Open alaendle opened 1 week ago

alaendle commented 1 week ago

Hi, first thank you for this library - I really love the approach ❤️. While I am playing around with modelling a data type that I can view both as a "whole" (Identity) and as "patch" (Maybe) - so that the can add patches (Semigroup) and apply them also to the whole object I stumbled around the following problem/question (but I'm pretty sure this is due to some misconception or limited knowledge on my side - but maybe you can point me to the right direction):

How can I combine bi-functors/nesting with covering/stripping?

To illustrate my problem:

data Foo f f' = Foo { foo :: f Int, fooBar :: f (Bar f') } deriving (Generic, FunctorB)

deriving instance FunctorT Foo
deriving instance ApplicativeT Foo

deriving instance (Show (f Int), Show (f (Bar f'))) => Show (Foo f f')
instance Alternative f => Semigroup (Foo f f') where
  (<>) = tzipWith (<|>)

instance Alternative f => Monoid (Foo f f') where
  mempty = tpure empty

data Bar f = Bar { bar :: f Int } deriving (Generic, FunctorB, ApplicativeB)

deriving instance Show (f Int) => Show (Bar f)

instance Alternative f => Semigroup (Bar f) where
  (<>) = bzipWith (<|>)

instance Alternative f => Monoid (Bar f) where
  mempty = bpure empty

This code works fine, but my problems begin to arise if I try to model using Wear to get rid of Identity for the "whole".

data Foo t f f' = Foo { foo :: Wear t f Int, fooBar :: Wear t f (Bar t f') } deriving (Generic)

deriving instance FunctorB (Foo Bare f)
deriving instance Functor f => FunctorB (Foo Covered f)
deriving instance FunctorT (Foo Covered)
deriving instance ApplicativeT (Foo Covered)

deriving instance (Show (Wear t f (Bar t f')), Show (Wear t f Int)) => Show (Foo t f f')
instance Alternative f => Semigroup (Foo Covered f f') where
  (<>) = tzipWith (<|>)

instance Alternative f => Monoid (Foo Covered f f') where
  mempty = tpure empty

data Bar t f = Bar { bar :: Wear t f Int } deriving (Generic)

deriving instance FunctorB (Bar Bare)
deriving instance FunctorB (Bar Covered)
deriving instance ApplicativeB (Bar Covered)
deriving instance Show (Wear t f Int) => Show (Bar t f)
instance BareB Bar
instance Alternative f => Semigroup (Bar Covered f) where
  (<>) = bzipWith (<|>)

instance Alternative f => Monoid (Bar Covered f) where
  mempty = bpure empty

And now to my problem - how to cover Foo?

works :: Bar Bare Identity -> Bar Covered Identity
works = bcover

how :: Foo Bare Identity Identity -> Foo Covered Identity Identity
how =bcover -- Couldn't match kind ‘* -> *’ with ‘*’ ...

For sure, by looking at the types it is pretty obvious why this couldn't work- however I'm relatively unsure if or how this could be resolved. As far as I get I couldn't implement BareB for Foo? Do "we" miss something like BareT?

Thanks in advance for any hint how to solve this and please let me know if I was unclear or could contribute further information.

alaendle commented 1 week ago

Maybe my last code-snippet with works and how signatures is a little bit misleading. Given a BareB instance I can write some "addition" for types like Bar as follows:

addB :: (BareB b, ApplicativeB (b Covered)) => b Bare Identity -> b Covered Maybe -> b Bare Identity
addB x dx = bstrip $ bzipWith fromMaybeI (bcover x) dx

fromMaybeI :: Identity a -> Maybe a -> Identity a
fromMaybeI (Identity a) Nothing  = Identity a
fromMaybeI _            (Just a) = Identity a

For sure I can implemented this for my nested Foo type "by-hand":

add :: Foo Bare Identity Identity -> Foo Covered Maybe Maybe -> Foo Bare Identity Identity
add (Foo x y) (Foo dx dy) = Foo (runIdentity $ fromMaybeI (Identity x) dx) (maybe y (addB y) dy)

But is it possible to write something similar to addB? I'm mean something that is in the same way generic.

jcpetruzza commented 6 days ago

As you say, in order to use Cover/Bare with with Foo we'd need something like BareT. In the end I never used BareB too much myself so it got all probably a bit neglected when I added the "transformers" part of the api (instead of BareB I tend to just use some accessor function that strips the Identity, and was hoping Unsaturated Type Families would eventually make it to the language). At the moment I don't have much time to look into it, but if you or someone else would like to submit a PR that adds BareT, I'd happily accept it :)

alaendle commented 6 days ago

Thanks for your quick reply. While it is easy to "copy" BareB for BareT I fear the GenericsN stuff is above my proficiency level. I'll need a instance like:

barbies-2.1.1.0:Barbies.Generics.Bare.GBare
                         0
                         (Rec
                            (Data.Functor.Identity.Identity
                               (Nested2FW
                                  Covered
                                  (barbies-2.1.1.0:Data.Generics.GenericN.Param
                                     0 Data.Functor.Identity.Identity)))
                            (Data.Functor.Identity.Identity
                               (Nested2FW Covered Data.Functor.Identity.Identity)))
                         (Rec
                            (Nested2FW
                               Bare
                               (barbies-2.1.1.0:Data.Generics.GenericN.Param
                                  0 Data.Functor.Identity.Identity))
                            (Nested2FW Bare Data.Functor.Identity.Identity))’

Guess the problem is that it is not obvious for GHC that Param 0 Identity is actual Identity and therefore a simple instance like

Coercible a b => GBare n (Rec (Identity a) (Identity a)) (Rec b b)

could match.

jcpetruzza commented 5 days ago

I fear the GenericsN stuff is above my proficiency level

If it's any consolation, I need to think again from the beginning how this works every time I work on it :)

Not sure how useful it will be without the commentary, but here are some old slides that I used to explain the general idea behind GenericN.

As a first step, what I'd do is study a bit how the generic implementation of FunctorB and FunctorT differ, as that will probably guide you into what needs to be done for BareB / BareT?

alaendle commented 3 days ago

Thank you, a really good introduction to the subject. But I'm still getting a bit confused. At the moment I can understand it more or less for each of the paths - but somehow I need both simultaneously (in order to be able to switch between Covered and Bare in a type-safe way); and so I'm not sure whether I don't have to change even more deeply. I'm thinking of the FilterIndex or the use in RepP - or I've completely lost track. I'm afraid I'd have to rebuild it from scratch to really understand the underlying mechanisms - I'm not sure I want to go down that path anymore 🤔