Skip to content

Add Co constructor for turning products into sums and vice versa #37

@masaeedu

Description

@masaeedu

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions