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:
newtype Flip t a b = Flip { unFlip :: t b a }
newtype Co b = Co { runCo :: forall r. b (Flip (->) r) -> r }
Given a sum type, like this:
data Something f = TheInt (f Int) | TheString (f String)
You get a record:
getTheInt :: Co Something -> Int
getTheInt (Co f) = f $ TheInt $ Flip id
getTheString :: Co Something -> String
getTheString (Co f) = f $ TheString $ Flip id
buildSomething :: (x -> Int) -> (x -> String) -> x -> Co Something
buildSomething f g x = Co $ \case
TheInt (Flip fi) -> fi $ f x
TheString (Flip fs) -> fs $ g x
And given a record type, like so:
data SomethingElse f = SomethingElse { theInt :: f Int, theString :: f String }
You get a sum:
buildAnInt :: Int -> Co SomethingElse
buildAnInt i = Co $ \SomethingElse { theInt } -> unFlip theInt i
buildAString :: String -> Co SomethingElse
buildAString s = Co $ \SomethingElse { theString } -> unFlip theString s
matchSomethingElse :: (Int -> r) -> (String -> r) -> Co SomethingElse -> r
matchSomethingElse f g (Co c) = c $ SomethingElse
{ theInt = Flip f
, theString = Flip g
}
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.
type Co :: ((k -> *) -> *) -> (k -> *) -> *
newtype Co b f = Co { runCo :: forall r. b (Compose (Flip (->) r) f) -> r }
data Something f = TheInt (f Int) | TheString (f String)
getTheInt :: Co Something f -> f Int
getTheInt (Co f) = f $ TheInt $ Compose $ Flip id
getTheString :: Co Something f -> f String
getTheString (Co f) = f $ TheString $ Compose $ Flip id
buildSomething :: (x -> f Int) -> (x -> f String) -> x -> Co Something f
buildSomething f g x = Co $ \case
TheInt (Compose (Flip fi)) -> fi $ f x
TheString (Compose (Flip fs)) -> fs $ g x
data SomethingElse f = SomethingElse { theInt :: f Int, theString :: f String }
buildAnInt :: f Int -> Co SomethingElse f
buildAnInt i = Co $ \SomethingElse { theInt = Compose (Flip x) } -> x i
buildAString :: f String -> Co SomethingElse f
buildAString s = Co $ \SomethingElse { theString = Compose (Flip x) } -> x s
matchSomethingElse :: (f Int -> r) -> (f String -> r) -> Co SomethingElse f -> r
matchSomethingElse f g (Co c) = c $ SomethingElse
{ theInt = Compose $ Flip f
, theString = Compose $ Flip g
}
For any product/coproduct type (and I speculate generally for any limit/colimit type), this should form a nice involution:
fwd :: Co (Co Something) f -> Something f
fwd (Co c) = c $ Co $ \case
TheInt (Compose (Flip fi)) -> fi $ Compose $ Flip TheInt
TheString (Compose (Flip fs)) -> fs $ Compose $ Flip TheString
bwd :: Something f -> Co (Co Something) f
bwd = \case
TheInt fi -> Co $ \(Co c) -> c $ TheInt $ Compose $ Flip $ \(Compose (Flip f)) -> f fi
TheString fs -> Co $ \(Co c) -> c $ TheString $ Compose $ Flip $ \(Compose (Flip f)) -> f fs
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.