Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
alaendle opened this issue Nov 13, 2024 · 5 comments
Open

Comments

@alaendle
Copy link

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
Copy link
Author

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
Copy link
Owner

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
Copy link
Author

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
Copy link
Owner

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
Copy link
Author

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 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants