Skip to content

Instantly share code, notes, and snippets.

@viercc
Last active October 16, 2022 09:24
Show Gist options
  • Save viercc/3d5aa3b0e8edb492341699d2c2d8392c to your computer and use it in GitHub Desktop.
Save viercc/3d5aa3b0e8edb492341699d2c2d8392c to your computer and use it in GitHub Desktop.
CoSemigroup

Related to: CoSemigroup and random generator transformers? - r/haskell

class CoSemigroup a where
    comult :: a -> (a,a)
    comult a = (projL a, projR a)
    
    projL :: a -> a
    projL = fst . comult
    
    projR :: a -> a
    projR = snd . comult
    
    -- Laws:
    --   projL . projL = projL
    --   projR . projR = projR
    --   porjL . projR = projR . projL

Finite sum of CoSemigroup is CoSemigroup again:

type (+) = Either

instance (CoSemigroup a, CoSemigroup b) => CoSemigroup (a + b) where
    comult (Left a) = bimap Left Left (comult a)
    comult (Right b) = bimap Right Right (comult b)

instance CoSemigroup Void where
    comult = absurd

There is a way to define CoSemigroup out of law-less pair of functions.

class Split c l r | c -> l r where
    toL :: c -> Maybe l
    toR :: c -> Maybe r

data CoSemi c l r = C c | L l | R r | Term

toL', toR' :: Split c l r => c -> CoSemi c l r
toL' = maybe Term L . toL
toR' = maybe Term R . toR

instance Split c l r => CoSemigroup (CoSemi c l r) where
    comult (C c) = (toL' c, toR' c)
    comult (L l) = (L l, Term)
    comult (R r) = (Term, R r)
    comult Term = (Term, Term)

Construction from Split is in some way canonical.

Any finite CoSemigroup A is isomorphic to sum

A ~ CoSemi C0 L0 R0 + CoSemi C1 L1 R1 + ... + CoSemi C_n L_n R_n

   (C0, C1, ... are instances of Split)

Proof: Let projLR = projL . projR.

projLR :: A -> A
projLR = projL . projR

Partition A to inverse images of projLR. comult is closed under each of the inverse images, so A is isomorphic to sum of these inverse images.

For each inverse images A_i = { a : A | projLR a = i }, partition A_i into four parts:

A_i = C_i + L_i + R_i + {i}
  where
    L_i = projL(A_i) \\ {i}
    R_i = projR(A_i) \\ {i}
    C_i = A_i \\ (L_i + R_i + {i})

Note that because projL(A_i) ∩ projR(A_i) == {i}, three sets L_i, R_i, {i} are disjoint each other.

When restricted to C_i, the range of projL and projR are included in L_i + {i} and R_i + {i}, respectively.

projL' :: C_i -> L_i + {i}
projL' = projL
projR' :: C_i -> R_i + {i}
projR' = projR

Using this function, Split C_i L_i R_i instance can be implemented.

instance Split C_i L_i R_i where
    toL :: C_i -> Maybe L_i
    toL c = case projL' c of
       l | l == i    -> Nothing
         | ohterwise -> Just l
    toR :: C_i -> Maybe R_i
    toR c = case projR' c of
       r | r == i    -> Nothing
         | ohterwise -> Just r

Then CoSemi C_i L_i R_i is isomorphic to A_i as CoSemigroup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment