-
Notifications
You must be signed in to change notification settings - Fork 15
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
Add Co
constructor for turning products into sums and vice versa
#37
Comments
Happy to PR this if you think this makes sense and can tell me where I should put it. |
Co
constructor for turning records into products and vice versaCo
constructor for turning products into sums and vice versa
Ok, I'm still digesting this! 😃 Let me see if I got it straight... If we think of a datatype as built from sums and products, let's call the "dual" of a type
Let's consider the possible cases:
Using algebraic identities plus
Now, for the second version of
But for this, IIUC, we need to define it differently. We'd like to use
Of course
Does this make sense? I may have misunderstood the second version. In any case, I found this transformation surprising, very cool! Do you have possible use cases in mind? I guess, one would then need a way to work generically with a type and it's dual, but I'm not sure what operations would be useful then. Maybe having a generic way to go from, say, a lens on a type to a prism on it's dual and vice-versa? Not sure... |
Hello @jcpetruzza, thanks for looking into this. I'm not sure I follow the syntax you're using for your reasoning here:
What does |
Sorry this was a bit confusing. The full proof of the equivalence would be an induction on the structure of the type, here I'm just looking at the thee main cases. The first one would be the induction base, when B f is isomorphic to f X for some type X (this is what I meant with |
Ah, nice, sorry for being dense. I still have to do some digesting of your comment to understand why An interesting perspective that occurred to me today while thinking about this is that an HKD is simply a continuation-monad-kinded type. The reason this jumped out at me is because the "base case" type constructor that you are working with is simply the return/pure operation of this monad. Here's some of the equipment that obviously falls out of this perspective: type HKD k = (k -> *) -> *
type BPure :: k -> HKD k
newtype BPure a cb = BPure { unBPure :: cb a }
type BJoin :: HKD (HKD k) -> HKD k
newtype BJoin kka cb = BJoin { unBJoin :: kka (BPure cb) }
type BFMap :: (a -> b) -> HKD a -> HKD b
newtype BFMap ab ka cb = BFMap { unBFMap :: ka (Compose cb ab) }
type BFFMap :: HKD a -> (a -> b) -> HKD b
newtype BFFMap ka ab cb = BFFMap { unBFFMap :: BFMap ab ka cb }
type BBind :: (a -> HKD b) -> HKD a -> HKD b
type BBind f k = BJoin (BFMap f k)
type BLift2 :: (a -> b -> c) -> HKD a -> HKD b -> HKD c
newtype BLift2 f kba kbb cb = BLift2 { unBLift2 :: BBind (BFFMap kbb) (BFMap f kba) cb }
type BProd :: HKD Type -> HKD Type -> HKD Type
type BProd = BLift2 (,)
type BSum :: HKD Type -> HKD Type -> HKD Type
type BSum = BLift2 Either And here are some proofs of lawfulness: data Iso p a b = Iso { fwd :: p a b, bwd :: p b a }
type FCoercible f = (forall a b. Coercible a b => Coercible (f a) (f b) :: Constraint)
type BCoercible k = (forall f g. (FCoercible f, FCoercible g) => Coercible (k f) (k g) :: Constraint)
coercible :: Coercible a b => Iso (->) a b
coercible = Iso { fwd = coerce, bwd = coerce }
--------------------------------------------------------------------------------
-- Left unit law
type LUnit1 :: HKD a -> HKD a
newtype LUnit1 k cb = LUnit1 { unLUnit1 :: BJoin (BPure k) cb }
type LUnit2 :: HKD a -> HKD a
newtype LUnit2 k cb = LUnit2 { unLUnit2 :: k cb }
lunit :: Iso (->) (LUnit1 k cb) (LUnit2 k cb)
lunit = coercible
--------------------------------------------------------------------------------
-- Right unit law
type RUnit1 :: HKD Type -> HKD Type
newtype RUnit1 k cb = RUnit1 { unRUnit1 :: BJoin (BFMap BPure k) cb }
type RUnit2 :: HKD Type -> HKD Type
newtype RUnit2 k cb = RUnit2 { unRUnit2 :: k cb }
runit :: (FCoercible cb, BCoercible k) => Iso (->) (RUnit1 k cb) (RUnit2 k cb)
runit = coercible
--------------------------------------------------------------------------------
-- Associativity law
type Assoc1 :: HKD (HKD (HKD a)) -> HKD a
newtype Assoc1 k cb = Assoc1 { unAssoc1 :: BJoin (BJoin k) cb }
type Assoc2 :: HKD (HKD (HKD a)) -> HKD a
newtype Assoc2 k cb = Assoc2 { unAssoc2 :: BJoin (BFMap BJoin k) cb }
assoc :: (FCoercible cb, BCoercible k) => Iso (->) (Assoc1 k cb) (Assoc2 k cb)
assoc = coercible |
@jcpetruzza Sorry about the long delay getting back to you. So to pick a random place to resume this:
So having thought about it a bit I'm not sure I agree that reformulation is what we'd want. But before I get into that, it's worth noting that the original formulation of type K k = (k -> Type) -> Type
type Co :: K k -> K k
newtype Co b f = Co {runCo :: forall r. b (Compose (Flip (->) r) f) -> r}
instance FunctorB b => FunctorB (Co b) where
bmap f (Co proj) = Co $ proj . bmap (\(Compose (Flip v)) -> Compose $ Flip $ v . f) Now as far as which formulation to use, it helps to get a bit less abstract and think about finite products and coproducts of things as HKDs. We can write these roughly as follows: -- A "heterogenization" of `Nat`
type HList :: [k] -> K k
data HList as f
where
HNil :: HList '[] f
HCons :: f x -> HList xs f -> HList (x ': xs) f
-- A "heterogenization" of `Fin`
type HItem :: [k] -> K k
data HItem as f
where
HHere :: f x -> HItem (x ': xs) f
HThere :: HItem xs f -> HItem (x ': xs) f The question we'd like to answer is:
It seems reasonable to say that up to isomorphism, there can at most one such Now I haven't proved that the original On the other hand, I've been stuck on the following implausible looking definition for the other version of newtype Co b f = Co {runCo :: forall r. b (Flip (->) r) -> f r}
fwd :: Co (HList '[]) f -> HItem '[] f
fwd (Co f) = _ $ f HNil I'm now required to produce a |
I was wondering if folks were still pursuing this feature. I've been using Barbies a lot to define flexible records that can hold things like raw values, mappings, etc. I've then collected these records into a sum so I can have a closed universe of such records. It would be extremely helpful to be able to turn that sum into a record that covers each case which I currently have to do manually. For example: data A = A
{ a1 :: f Text,
a2 :: f Bool
}
data B = B
{ b1 :: f Int
}
-- Hold a single instance of any of the known records
data MyRecs f = MyRecs_A (A f) | MyRecs_B (B f)
-- Hold something simultaneously for each type of record. Would be nice to derive that from MyRecs
data AllRecs f = AllRecs
{
allRecs_A :: A f,
allRecs_B :: B f
} I was just wondering if this is something that this issue would allow for or if there's a different way to achieve what I'm after. |
Hi @MichaelXavier. Yes. In fact you can take your pick of |
Ah wait, it might not work for the nested records 🤔 . |
The limitation as it's stated that it only works for types that are overall a pure product or coproduct. But the types above have somewhat unexpected decompositions as co/products:
So our dumb
Unfortunately this is:
And It's possible that there's no way to avoid this "piecewise" duality without switching to some kind of description of the datatype. |
I appreciate you walking through that! Fair enough. I'll probably get fewer strange looks from coworkers if I just explicitly define the type. Thanks! |
Hi there. Not sure if I was looking carefully enough, but I wasn't able to find an equivalent for this type constructor that is sometimes useful:
Given a sum type, like this:
You get a record:
And given a record type, like so:
You get a sum:
Assuming the idea is clear enough, it's more aesthetically pleasing to "keep things closed" by parametrizing
Co
further, so that it produces something of the same kind as what it consumes.For any product/coproduct type (and I speculate generally for any limit/colimit type), this should form a nice involution:
We need some kind of typeclass to represent limits/colimits before we can write this more polymorphically, but I'm fairly confident that it works for all products/coproducts at least.
The text was updated successfully, but these errors were encountered: