packages feed

coapplicative-0.1.0.0: src/Control/CoApplicative.hs

{-# LANGUAGE DeriveFunctor, TypeOperators, FlexibleContexts, UndecidableInstances #-}

-- | Provides CoApplicative typeclass and instances.
module Control.CoApplicative (CoApplicative(..), CoAppComonad(..)) where

import Control.Comonad
import Control.Comonad.Trans.Env
import Data.Void
import Data.Functor.Identity (Identity(..))
import Data.List.NonEmpty
import Data.Maybe (mapMaybe)
import Data.Bifunctor
import Data.Functor.Sum
import Data.Coerce
import GHC.Generics

leftToMaybe :: Either a b -> Maybe a
leftToMaybe (Left x) = Just x
leftToMaybe (Right _) = Nothing
rightToMaybe :: Either a b -> Maybe b
rightToMaybe (Left _) = Nothing
rightToMaybe (Right x) = Just x

-- | An opmonoidal functor over the cocartesian structure
-- of Either and Void.
--
-- Laws include associativity, and compatibility with fmap
-- (which implies identity laws)
--
-- either id split . split = either split id . split . fmap reassoc
-- where reassoc is the unique total function of type (Either a (Either b c)) -> Either (Either a b) c
-- split . fmap (either f g) = either (fmap f) (fmap g) . split
-- split . fmap Left = Left
-- split . fmap Right = Right
--
-- Every Comonad is a CoApplicative, but not always in a compatible way
-- with the Comonad structure.
-- In particular, duplicate must distribute with split:
--
-- bimap duplicate duplicate (split wab) = split (fmap split (duplicate wab))
--
class Functor f => CoApplicative f where
  nonempty :: f Void -> Void
  split :: f (Either a b) -> Either (f a) (f b)

  -- | Filter Maybe through the data-structure along Just,
  -- discarding the context of Nothing values
  --
  -- The default implementation biases towards the Left
  splitMaybe :: f (Maybe a) -> Maybe (f a)
  splitMaybe = leftToMaybe . split . fmap maybeToLeft
    where
      maybeToLeft (Just x) = Left x
      maybeToLeft Nothing = Right ()

  -- | Zip a list through the data-structure,
  -- discarding the context of nil values.
  -- I.e. each position in the resulting
  -- list will "collect" the corresponding f a
  splitList :: f [a] -> [f a]
  splitList = roll . maybe Nothing (Just . dorec) . splitMaybe . fmap unroll
    {- TODO: make this fuse? At least on its output -}
    where
      dorec was = (fmap fst was, splitList $ fmap snd was)

      unroll :: [b] -> Maybe (b, [b])
      unroll [] = Nothing
      unroll (x : xs) = Just (x, xs)
      roll :: Maybe (b, [b]) -> [b]
      roll Nothing = []
      roll (Just (x, xs)) = x : xs

instance CoApplicative Identity where
  nonempty (Identity v) = v
  split (Identity (Left x)) = Left (Identity x)
-- This is compatible
  split (Identity (Right y)) = Right (Identity y)

-- | Filters out elements which do not match the head.
instance CoApplicative NonEmpty where
  nonempty (v :| _) = v
  split (Left x :| rest) = Left (x :| mapMaybe leftToMaybe rest)
  split (Right x :| rest) = Right (x :| mapMaybe rightToMaybe rest)

instance (CoApplicative f, CoApplicative g) => CoApplicative (Sum f g) where
  nonempty (InL fv) = nonempty fv
  nonempty (InR gv) = nonempty gv
  split (InL fe) = bimap InL InL (split fe)
  split (InR ge) = bimap InR InR (split ge)
  splitMaybe (InL fm) = InL <$> (splitMaybe fm)
  splitMaybe (InR gm) = InR <$> (splitMaybe gm)
  splitList (InL fxs) = InL <$> (splitList fxs)
  splitList (InR gxs) = InR <$> (splitList gxs)

instance CoApplicative ((,) a) where
  nonempty (_, v) = v
  split (a, Left x) = Left (a, x)
  split (a, Right y) = Right (a, y)

instance CoApplicative ((,,) a b) where
  nonempty (_, _, v) = v
  split (a, b, Left x) = Left (a, b, x)
  split (a, b, Right y) = Right (a, b, y)

instance CoApplicative ((,,,) a b c) where
  nonempty (_, _, _, v) = v
  split (a, b, c, Left x) = Left (a, b, c, x)
  split (a, b, c, Right y) = Right (a, b, c, y)

instance CoApplicative ((,,,,) a b c d) where
  nonempty (_, _, _, _, v) = v
  split (a, b, c, d, Left x) = Left (a, b, c, d, x)
  split (a, b, c, d, Right y) = Right (a, b, c, d, y)

instance CoApplicative ((,,,,,) a b c d e) where
  nonempty (_, _, _, _, _, v) = v
  split (a, b, c, d, e, Left x) = Left (a, b, c, d, e, x)
  split (a, b, c, d, e, Right y) = Right (a, b, c, d, e, y)

instance CoApplicative ((,,,,,,) a b c d e f) where
  nonempty (_, _, _, _, _, _, v) = v
  split (a, b, c, d, e, f, Left x) = Left (a, b, c, d, e, f, x)
  split (a, b, c, d, e, f, Right y) = Right (a, b, c, d, e, f, y)

instance CoApplicative w => CoApplicative (EnvT e w) where
  nonempty (EnvT _ wv) = nonempty wv
  split (EnvT e we) = bimap (EnvT e) (EnvT e) (split we)
  splitMaybe (EnvT e wm) = EnvT e <$> splitMaybe wm
  splitList (EnvT e wxs) = EnvT e <$> splitList wxs

instance CoApplicative f => CoApplicative (M1 i c f) where
  nonempty (M1 fv) = nonempty fv
  split (M1 fab) = coerce (split fab)
  splitMaybe (M1 fa) = M1 <$> splitMaybe fa
  splitList (M1 fxs) = M1 <$> splitList fxs

-- identical to Sum
instance (CoApplicative f, CoApplicative g) => CoApplicative (f :+: g) where
  nonempty (L1 fv) = nonempty fv
  nonempty (R1 gv) = nonempty gv
  split (L1 fe) = bimap L1 L1 (split fe)
  split (R1 ge) = bimap R1 R1 (split ge)
  splitMaybe (L1 fm) = L1 <$> (splitMaybe fm)
  splitMaybe (R1 gm) = R1 <$> (splitMaybe gm)
  splitList (L1 fxs) = L1 <$> (splitList fxs)
  splitList (R1 gxs) = R1 <$> (splitList gxs)

instance (CoApplicative f, CoApplicative g) => CoApplicative (f :.: g) where
  nonempty (Comp1 fgv) = nonempty (nonempty <$> fgv)
  split (Comp1 fgab) =
    coerce $
    split (fmap split fgab)
  splitMaybe (Comp1 fga) = fmap Comp1 $ splitMaybe $ fmap splitMaybe fga
  splitList (Comp1 fgxs) = fmap Comp1 $ splitList $ fmap splitList fgxs

instance CoApplicative Par1 where
  nonempty (Par1 v) = v
  split (Par1 (Left a)) = Left (Par1 a)
  split (Par1 (Right a)) = Right (Par1 a)
  splitMaybe (Par1 m) = Par1 <$> m
  splitList (Par1 xs) = Par1 <$> xs

instance CoApplicative f => CoApplicative (Rec1 f) where
  nonempty (Rec1 fv) = nonempty fv
  split (Rec1 fab) = coerce (split fab)
  splitMaybe (Rec1 fa) = coerce $ splitMaybe fa
  splitList (Rec1 fxs) = coerce $ splitList fxs

instance (Generic1 f, CoApplicative (Rep1 f)) => CoApplicative (Generically1 f) where
  nonempty (Generically1 fa) = nonempty (from1 fa)
  split (Generically1 fab) =
    bimap (Generically1 . to1) (Generically1 . to1)
    (split (from1 fab))
  splitMaybe (Generically1 fa) = fmap Generically1 $ fmap to1 $ splitMaybe $ from1 fa
  splitList (Generically1 fxs) = fmap Generically1 $ fmap to1 $ splitList $ from1 fxs

-- | There is a derivable instance for any Comonad,
-- but this will not be compatible with context shifts for most instances.
-- 
-- In the context of pattern-matching, this means that reaching the same branch
-- two different ways may result in conflicting views of the surrounding context.
-- (only the context which lands on the same side of the branch is consistent)
newtype CoAppComonad w a = CoAppComonad { runCoAppComonad :: w a } deriving (Functor)

instance Comonad w => CoApplicative (CoAppComonad w) where
  nonempty (CoAppComonad wv) = extract wv
  split (CoAppComonad wab) =
    case extract wab of
      Left x -> Left (CoAppComonad $ fmap (either id (const x)) wab)
      Right y -> Right (CoAppComonad $ fmap (either (const y) id) wab)

instance Comonad w => Comonad (CoAppComonad w) where
  extract = extract . runCoAppComonad
  {- coerce gets blocked by unknown roles sadly -}
  duplicate (CoAppComonad wa) = CoAppComonad (fmap CoAppComonad (duplicate wa))