From 2d4262b8a1db520865b2ebad83bfd3dc491bdac2 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 12:22:19 +1000 Subject: [PATCH 01/15] Rename ProductProfunctor -> SemiproductProfunctor --- CHANGELOG.md | 4 ++ Data/Profunctor/Product.hs | 53 ++++++++++++++------- Data/Profunctor/Product/Class.hs | 8 ++-- Data/Profunctor/Product/Default/Class.hs | 16 +++---- Data/Profunctor/Product/Examples.hs | 8 ++-- Data/Profunctor/Product/Internal/Adaptor.hs | 8 ++-- Data/Profunctor/Product/Internal/TH.hs | 6 +-- Data/Profunctor/Product/Tuples/TH.hs | 6 +-- Test/CheckTypes.hs | 28 +++++------ Test/Definitions.hs | 8 ++-- Test/DefinitionsUndecidable.hs | 8 ++-- 11 files changed, 89 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d606df6..082d457e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased (major) + +* Rename `ProductProfunctor` to `SemiProductProfunctor`. + # 0.11.1.1 * No user-visible changes diff --git a/Data/Profunctor/Product.hs b/Data/Profunctor/Product.hs index 4f6f23d8..7eec86cc 100644 --- a/Data/Profunctor/Product.hs +++ b/Data/Profunctor/Product.hs @@ -1,8 +1,29 @@ {-# OPTIONS_GHC -Wno-orphans #-} {-# LANGUAGE TemplateHaskell #-} +-- | If @p@ is an instance of 'SemiproductProfunctor' then @p a a'@ +-- represents a sort of process for turning @a@s into @a'@s that can +-- be "laid out side-by-side" with other values of @p@ to form "wider" +-- processes. For example, if I have +-- +-- @ +-- a :: p a a' -- a process for turning as into a's +-- b :: p b b' -- a process for turning bs into b's +-- c :: p c c' -- a process for turning cs into c's +-- @ +-- +-- then I can combine them using 'p3' to get +-- +-- @ +-- p3 a b c :: p (a, b, c) (a', b', c') +-- -- a process for turning (a, b, c)s into (a', b', c')s +-- @ +-- +-- You would typically compose 'SemiproductProfunctor's using +-- 'Profunctors''s 'Profunctor.lmap' and 'Applicative''s 'pure', +-- '<$>' / 'fmap' and '<*>'. module Data.Profunctor.Product (-- * @ProductProfunctor@ - ProductProfunctor(..), + SemiproductProfunctor(..), (***$), -- * @SumProfunctor@ SumProfunctor(..), @@ -45,7 +66,7 @@ import Data.Profunctor.Product.Flatten import Data.Profunctor.Product.Tuples import Data.Profunctor.Product.Tuples.TH (pTns, maxTupleSize, pNs) --- ProductProfunctor and ProductContravariant are potentially +-- SemiproductProfunctor and ProductContravariant are potentially -- redundant type classes. It seems to me that these are equivalent -- to Profunctor with Applicative, and Contravariant with Monoid -- respectively: @@ -76,7 +97,7 @@ import Data.Profunctor.Product.Tuples.TH (pTns, maxTupleSize, pNs) -- Profunctor+Applicative or Contravariant+Monoid approach we do not -- have a guarantee that these operations are polymorphic. -- --- Previously I wanted to replace ProductProfunctor and +-- Previously I wanted to replace SemiproductProfunctor and -- ProductContravariant entirely. This proved difficult as it is not -- possible to expand the class constraints to require Applicative and -- Monoid respectively. We can't enforce a constraint 'Applicative (p @@ -101,53 +122,53 @@ import Data.Profunctor.Product.Tuples.TH (pTns, maxTupleSize, pNs) (***$) :: Profunctor p => (b -> c) -> p a b -> p a c (***$) = Profunctor.rmap -instance ProductProfunctor (->) where +instance SemiproductProfunctor (->) where purePP = pure (****) = (<*>) -instance Arrow arr => ProductProfunctor (WrappedArrow arr) where +instance Arrow arr => SemiproductProfunctor (WrappedArrow arr) where empty = id (***!) = (***) -instance ProductProfunctor Tagged where +instance SemiproductProfunctor Tagged where purePP = pure (****) = (<*>) -instance Applicative f => ProductProfunctor (Star f) where +instance Applicative f => SemiproductProfunctor (Star f) where purePP = pure (****) = (<*>) -instance Functor f => ProductProfunctor (Costar f) where +instance Functor f => SemiproductProfunctor (Costar f) where purePP = pure (****) = (<*>) -- | @since 0.11.1.0 -instance Monoid r => ProductProfunctor (Forget r) where +instance Monoid r => SemiproductProfunctor (Forget r) where purePP _ = Forget (const mempty) Forget f ***! Forget g = Forget $ \(a, a') -> f a <> g a' -instance (ProductProfunctor p, ProductProfunctor q) => ProductProfunctor (Procompose p q) where +instance (SemiproductProfunctor p, SemiproductProfunctor q) => SemiproductProfunctor (Procompose p q) where purePP a = Procompose (purePP a) (purePP ()) Procompose pf qf **** Procompose pa qa = Procompose (lmap fst pf **** lmap snd pa) ((,) ***$ qf **** qa) -instance (Functor f, Applicative g, ProductProfunctor p) => ProductProfunctor (Biff p f g) where +instance (Functor f, Applicative g, SemiproductProfunctor p) => SemiproductProfunctor (Biff p f g) where purePP = Biff . purePP . pure Biff abc **** Biff ab = Biff $ (<*>) ***$ abc **** ab -instance Applicative f => ProductProfunctor (Joker f) where +instance Applicative f => SemiproductProfunctor (Joker f) where purePP = Joker . pure Joker bc **** Joker b = Joker $ bc <*> b -instance Divisible f => ProductProfunctor (Clown f) where +instance Divisible f => SemiproductProfunctor (Clown f) where purePP _ = Clown conquer Clown l **** Clown r = Clown $ divide (\a -> (a, a)) l r -instance (ProductProfunctor p, ProductProfunctor q) => ProductProfunctor (Product p q) where +instance (SemiproductProfunctor p, SemiproductProfunctor q) => SemiproductProfunctor (Product p q) where purePP a = Pair (purePP a) (purePP a) Pair l1 l2 **** Pair r1 r2 = Pair (l1 **** r1) (l2 **** r2) -instance (Applicative f, ProductProfunctor p) => ProductProfunctor (Tannen f p) where +instance (Applicative f, SemiproductProfunctor p) => SemiproductProfunctor (Tannen f p) where purePP = Tannen . pure . purePP Tannen f **** Tannen a = Tannen $ liftA2 (****) f a @@ -185,7 +206,7 @@ instance (Applicative f, SumProfunctor p) => SumProfunctor (Tannen f p) where -- in spirit, a generalisation of @traverse :: (a -> f b) -> [a] -> f -- [b]@, but the types need to be shuffled around a bit to make that -- work. -list :: (ProductProfunctor p, SumProfunctor p) => p a b -> p [a] [b] +list :: (SemiproductProfunctor p, SumProfunctor p) => p a b -> p [a] [b] list p = Profunctor.dimap fromList toList (empty +++! (p ***! list p)) where toList :: Either () (a, [a]) -> [a] toList = either (const []) (uncurry (:)) diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index c8c11b56..6e1caf6f 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -76,12 +76,12 @@ import qualified Data.Profunctor as Profunctor -- 'purePP' = 'Control.Applicative.pure' -- ('****') = ('Control.Applicative.<*>') -- @ -class Profunctor p => ProductProfunctor p where +class Profunctor p => SemiproductProfunctor p where -- | 'purePP' is the generalisation of @Applicative@'s -- 'Control.Applicative.pure'. -- -- (You probably won't need to use this except to define - -- 'ProductProfunctor' instances. In your own code @pure@ should be + -- 'SemiproductProfunctor' instances. In your own code @pure@ should be -- sufficient.) purePP :: b -> p a b purePP b = Profunctor.dimap (const ()) (const b) empty @@ -90,8 +90,8 @@ class Profunctor p => ProductProfunctor p where -- 'Control.Applicative.<*>'. -- -- (You probably won't need to use this except to define - -- 'ProductProfunctor' instances. In your own code @\<*\>@ should - -- be sufficient.) + -- 'SemiproductProfunctor' instances. In your own code @\<*\>@ + -- should be sufficient.) (****) :: p a (b -> c) -> p a b -> p a c (****) f x = Profunctor.dimap dup (uncurry ($)) (f ***! x) where dup y = (y, y) diff --git a/Data/Profunctor/Product/Default/Class.hs b/Data/Profunctor/Product/Default/Class.hs index edd7bb28..19a79fd6 100644 --- a/Data/Profunctor/Product/Default/Class.hs +++ b/Data/Profunctor/Product/Default/Class.hs @@ -36,16 +36,16 @@ type DefaultFields p a b = GDefCnstr p (Rep a) (Rep b) type DefaultFields' p a = DefaultFields p a a -- | @'DefaultPConstraints' p a@ expands to the minimal combination of --- @'Profunctor' p@, @'ProductProfunctor' p@, @'SumProfunctor' p@ needed to implement +-- @'Profunctor' p@, @'SemiproductProfunctor' p@, @'SumProfunctor' p@ needed to implement -- the instance @'Default' p a a@ for a 'Generic' datatype @a@. -- -- > DefaultPConstraints p Foo = --- > ( ProductProfunctor p -- because Foo has a constructor Bar with many fields --- > , SumProfunctor p -- because Foo has multiple constructors +-- > ( SemiproductProfunctor p -- because Foo has a constructor Bar with many fields +-- > , SumProfunctor p -- because Foo has multiple constructors -- > ) -- -- > DefaultConstraints p (a, b) = --- > ( ProductProfunctor p -- (a, b) has a single constructor with two fields +-- > ( SemiproductProfunctor p -- (a, b) has a single constructor with two fields -- > ) type DefaultPConstraints p a = GDefPCnstr p (Rep a) @@ -74,7 +74,7 @@ class GDefault p f g where type GDefCnstr p f g :: Constraint gdef1 :: p (f a) (g a) -instance ProductProfunctor p => GDefault p U1 U1 where +instance SemiproductProfunctor p => GDefault p U1 U1 where type GDefCnstr p U1 U1 = () gdef1 = dimap (const ()) (const U1) empty @@ -86,7 +86,7 @@ instance (Profunctor p, Default p c c') => GDefault p (K1 i c) (K1 i c') where type GDefCnstr p (K1 i c) (K1 i c') = Default p c c' gdef1 = dimap unK1 K1 def -instance (ProductProfunctor p, GDefault p f f', GDefault p g g') => GDefault p (f :*: g) (f' :*: g') where +instance (SemiproductProfunctor p, GDefault p f f', GDefault p g g') => GDefault p (f :*: g) (f' :*: g') where type GDefCnstr p (f :*: g) (f' :*: g') = (GDefCnstr p f f', GDefCnstr p g g') gdef1 = dimap (\(x :*: y) -> (x, y)) (uncurry (:*:)) $ gdef1 ***! gdef1 @@ -102,10 +102,10 @@ instance (SumProfunctor p, GDefault p f f', GDefault p g g') => GDefault p (f :+ R1 x -> Right x type family GDefPCnstr (p :: * -> * -> *) (f :: * -> *) :: Constraint -type instance GDefPCnstr p U1 = ProductProfunctor p +type instance GDefPCnstr p U1 = SemiproductProfunctor p type instance GDefPCnstr p (M1 i c f) = GDefPCnstr p f type instance GDefPCnstr p (K1 i c) = Profunctor p -type instance GDefPCnstr p (f :*: g) = ProductProfunctor p +type instance GDefPCnstr p (f :*: g) = SemiproductProfunctor p type instance GDefPCnstr p (f :+: g) = (SumProfunctor p, GDefPCnstr p f, GDefPCnstr p g) gdef :: (Profunctor p, Generic a, Generic b, GDefault p (Rep a) (Rep b)) => p a b diff --git a/Data/Profunctor/Product/Examples.hs b/Data/Profunctor/Product/Examples.hs index 025b094e..7842cdda 100644 --- a/Data/Profunctor/Product/Examples.hs +++ b/Data/Profunctor/Product/Examples.hs @@ -48,7 +48,7 @@ instance Applicative f => Applicative (Replicator r f a) where instance Functor f => P.Profunctor (Replicator r f) where dimap _ h (Replicator f) = Replicator ((fmap . fmap) h f) -instance Applicative f=> PP.ProductProfunctor (Replicator r f) where +instance Applicative f=> PP.SemiproductProfunctor (Replicator r f) where purePP = pure (****) = (<*>) @@ -93,7 +93,7 @@ instance Applicative (Take a z) where instance P.Profunctor (Take a) where dimap _ g (Take h) = Take ((fmap . fmap . fmap) g h) -instance PP.ProductProfunctor (Take a) where +instance PP.SemiproductProfunctor (Take a) where purePP = pure (****) = (<*>) @@ -132,7 +132,7 @@ instance Applicative f => Applicative (Traverse f a) where instance Functor f => P.Profunctor (Traverse f) where dimap g h (Traverse f) = Traverse (P.dimap g (fmap h) f) -instance Applicative f => PP.ProductProfunctor (Traverse f) where +instance Applicative f => PP.SemiproductProfunctor (Traverse f) where purePP = pure (****) = (<*>) @@ -151,7 +151,7 @@ instance Applicative (Zipper a) where pure = Zipper . pure f <*> x = Zipper ((<*>) (unZipper f) (unZipper x)) -instance PP.ProductProfunctor Zipper where +instance PP.SemiproductProfunctor Zipper where purePP = pure (****) = (<*>) diff --git a/Data/Profunctor/Product/Internal/Adaptor.hs b/Data/Profunctor/Product/Internal/Adaptor.hs index bbc2cd22..d45cd55a 100644 --- a/Data/Profunctor/Product/Internal/Adaptor.hs +++ b/Data/Profunctor/Product/Internal/Adaptor.hs @@ -13,7 +13,7 @@ module Data.Profunctor.Product.Internal.Adaptor where import Data.Profunctor (Profunctor, dimap, lmap) -import Data.Profunctor.Product (ProductProfunctor, (****), (***$)) +import Data.Profunctor.Product (SemiproductProfunctor, (****), (***$)) import GHC.Generics (from, to, M1(M1), K1(K1), (:*:)((:*:)), Generic, Rep) @@ -23,9 +23,9 @@ import GHC.Generics (from, to, -- | Generic adaptor. -- -- @ --- 'genericAdaptor' :: 'ProductProfunctor' p => +-- 'genericAdaptor' :: 'SemiproductProfunctor' p => -- 'Adaptor' p (Foo (p a a') (p b b') (p c c')) --- 'genericAdaptor' :: 'ProductProfunctor' p => +-- 'genericAdaptor' :: 'SemiproductProfunctor' p => -- Foo (p a a') (p b b') (p c c') -> p (Foo a b c) (Foo a' b' c') -- @ genericAdaptor :: GAdaptable p a b c => a -> p b c @@ -105,7 +105,7 @@ class Profunctor p => GAdaptor p f | f -> p where gAdaptor :: f a -> p (GUnzip 'Fst f a) (GUnzip 'Snd f a) instance - (ProductProfunctor p, GAdaptor p f, GAdaptor p g) + (SemiproductProfunctor p, GAdaptor p f, GAdaptor p g) => GAdaptor p (f :*: g) where gAdaptor (f :*: g) = (:*:) ***$ lmap pfst (gAdaptor f) diff --git a/Data/Profunctor/Product/Internal/TH.hs b/Data/Profunctor/Product/Internal/TH.hs index b3ce2bc3..885de276 100644 --- a/Data/Profunctor/Product/Internal/TH.hs +++ b/Data/Profunctor/Product/Internal/TH.hs @@ -162,7 +162,7 @@ instanceDefinition side tyName' numTyVars numConVars adaptorName' conName = pure (productProfunctor_p' : typeMatch' ++ default_p_as0_as1) productProfunctor_p :: Q Pred - productProfunctor_p = classP ''ProductProfunctor [p] + productProfunctor_p = classP ''SemiproductProfunctor [p] (typeMatch, pArg0, pArg1) = case side of Nothing -> ([], tyName0, tyName1) @@ -191,7 +191,7 @@ adaptorSig tyName' numTyVars n = fmap (SigD n) adaptorType where p = mkName "p" adaptorType = ForallT scope <$> adaptorCxt <*> adaptorAfterCxt adaptorAfterCxt = [t| $before -> $after |] - adaptorCxt = fmap (:[]) (classP ''ProductProfunctor [pType]) + adaptorCxt = fmap (:[]) (classP ''SemiproductProfunctor [pType]) before = foldl (liftA2 AppT) (pure (ConT tyName')) pArgs pType = pure $ VarT p pArgs = map pApp tyVars @@ -356,7 +356,7 @@ toTuple conName = xTuple patCon retCon Note that we can also do the instance definition like this, but it would require pulling the to/fromTuples to the top level -instance (ProductProfunctor p, Default p a a', Default p b b', +instance (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c', Default p d d', Default p e e', Default p f f', Default p g g', Default p h h') => Default p (LedgerRow' a b c d e f g h) diff --git a/Data/Profunctor/Product/Tuples/TH.hs b/Data/Profunctor/Product/Tuples/TH.hs index d10bd91e..bf250897 100644 --- a/Data/Profunctor/Product/Tuples/TH.hs +++ b/Data/Profunctor/Product/Tuples/TH.hs @@ -13,7 +13,7 @@ import Language.Haskell.TH import Language.Haskell.TH.Datatype.TyVarBndr import Data.Profunctor (Profunctor (dimap)) -import Data.Profunctor.Product.Class (ProductProfunctor, (***!), empty) +import Data.Profunctor.Product.Class (SemiproductProfunctor, (***!), empty) import Data.Profunctor.Product.Default.Class (Default (def)) import Control.Applicative (pure) @@ -32,7 +32,7 @@ mkT n = tySynD (tyName n) tyVars tyDef applyT n' = foldl (\t v -> t `appT` varT v) (conT (tyName n')) (take n' (tail allNames)) allNames = [ mkName $ c:show i | i <- [0::Int ..], c <- ['a'..'z'] ] -chain :: ProductProfunctor p => (t -> p a2 b2) -> (p a1 b1, t) +chain :: SemiproductProfunctor p => (t -> p a2 b2) -> (p a1 b1, t) -> p (a1, a2) (b1, b2) chain rest (a, as) = a ***! rest as @@ -40,7 +40,7 @@ pTns :: [Int] -> Q [Dec] pTns = fmap concat . mapM pTn productProfunctor :: Name -> Q Pred -productProfunctor p = [t|ProductProfunctor $(v p)|] +productProfunctor p = [t|SemiproductProfunctor $(v p)|] where v = pure . VarT default_ :: Name -> Name -> Name -> Q Pred diff --git a/Test/CheckTypes.hs b/Test/CheckTypes.hs index 49562099..89245a58 100644 --- a/Test/CheckTypes.hs +++ b/Test/CheckTypes.hs @@ -3,7 +3,7 @@ module CheckTypes where -import Data.Profunctor.Product (ProductProfunctor) +import Data.Profunctor.Product (SemiproductProfunctor) import Data.Profunctor.Product.Default (Default, def) import Data.Profunctor.Product.Adaptor @@ -22,59 +22,59 @@ import DefinitionsUndecidable () -- that only the order in which the product profunctors are combined -- can vary. -pData2' :: ProductProfunctor p => +pData2' :: SemiproductProfunctor p => Data2 (p a a') (p b b') -> p (Data2 a b) (Data2 a' b') pData2' = pData2 -pData3' :: ProductProfunctor p => +pData3' :: SemiproductProfunctor p => Data3 (p a a') (p b b') (p c c') -> p (Data3 a b c) (Data3 a' b' c') pData3' = pData3 -pRecord2' :: ProductProfunctor p => +pRecord2' :: SemiproductProfunctor p => Record2 (p a a') (p b b') -> p (Record2 a b) (Record2 a' b') pRecord2' = pRecord2 -pRecord3' :: ProductProfunctor p => +pRecord3' :: SemiproductProfunctor p => Record3 (p a a') (p b b') (p c c') -> p (Record3 a b c) (Record3 a' b' c') pRecord3' = pRecord3 -instanceData2 :: (ProductProfunctor p, Default p a a', Default p b b') +instanceData2 :: (SemiproductProfunctor p, Default p a a', Default p b b') => p (Data2 a b) (Data2 a' b') instanceData2 = def -instanceData3 :: (ProductProfunctor p, +instanceData3 :: (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c') => p (Data3 a b c) (Data3 a' b' c') instanceData3 = def -instanceRecord2 :: (ProductProfunctor p, Default p a a', Default p b b') +instanceRecord2 :: (SemiproductProfunctor p, Default p a a', Default p b b') => p (Record2 a b) (Record2 a' b') instanceRecord2 = def -instanceRecord3 :: (ProductProfunctor p, +instanceRecord3 :: (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c') => p (Record3 a b c) (Record3 a' b' c') instanceRecord3 = def -defaultNameGenerated :: ProductProfunctor p => RecordDefaultName (p x x') (p y y') +defaultNameGenerated :: SemiproductProfunctor p => RecordDefaultName (p x x') (p y y') -> p (RecordDefaultName x y) (RecordDefaultName x' y') defaultNameGenerated = pRecordDefaultName -- We similarly test the type of the generic adaptor. -pData2G :: ProductProfunctor p => +pData2G :: SemiproductProfunctor p => Data2 (p a a') (p b b') -> p (Data2 a b) (Data2 a' b') pData2G = genericAdaptor -pData3G :: ProductProfunctor p => +pData3G :: SemiproductProfunctor p => Data3 (p a a') (p b b') (p c c') -> p (Data3 a b c) (Data3 a' b' c') pData3G = genericAdaptor -pRecord2G :: ProductProfunctor p +pRecord2G :: SemiproductProfunctor p => Record2 (p a a') (p b b') -> p (Record2 a b) (Record2 a' b') pRecord2G = pRecord2 -pRecord3G :: ProductProfunctor p +pRecord3G :: SemiproductProfunctor p => Record3 (p a a') (p b b') (p c c') -> p (Record3 a b c) (Record3 a' b' c') pRecord3G = pRecord3 diff --git a/Test/Definitions.hs b/Test/Definitions.hs index c5fdc0df..203fc212 100644 --- a/Test/Definitions.hs +++ b/Test/Definitions.hs @@ -48,16 +48,16 @@ $(makeAdaptorAndInstance' ''RecordDefaultName) instance Unzippable Data2 instance Unzippable Data3 -instance (ProductProfunctor p, Default p a a', Default p b b', Default p c c') +instance (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c') => Default p (DataGeneric a b c) (DataGeneric a' b' c') -instance (ProductProfunctor p, Default p a a', Default p b b', Default p c c') +instance (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c') => Default p (RecordGeneric a b c) (RecordGeneric a' b' c') instance (SumProfunctor p, Default p a a', Default p b b') => Default p (SumGeneric a b) (SumGeneric a' b') -instance (ProductProfunctor p, SumProfunctor p, Default p a a', Default p b b', Default p c c') +instance (SemiproductProfunctor p, SumProfunctor p, Default p a a', Default p b b', Default p c c') => Default p (ProductAndSumGeneric a b c) (ProductAndSumGeneric a' b' c') data Data2Inferrable a b = Data2Inferrable a b @@ -71,7 +71,7 @@ newtype Arrow a b = Arrow { unArrow :: a -> b } instance Profunctor Arrow where dimap f g = Arrow . dimap f g . unArrow -instance ProductProfunctor Arrow where +instance SemiproductProfunctor Arrow where purePP = Arrow . purePP f **** g = Arrow (unArrow f **** unArrow g) diff --git a/Test/DefinitionsUndecidable.hs b/Test/DefinitionsUndecidable.hs index b0ed410b..f078b006 100644 --- a/Test/DefinitionsUndecidable.hs +++ b/Test/DefinitionsUndecidable.hs @@ -18,7 +18,7 @@ module DefinitionsUndecidable where -- It's a bit sad that these need UndecidableInstances import GHC.Generics (Generic) -import Data.Profunctor.Product (ProductProfunctor, SumProfunctor) +import Data.Profunctor.Product (SemiproductProfunctor, SumProfunctor) import Data.Profunctor.Product.Default (Default, DefaultFields', DefaultConstraints, DefaultConstraints') @@ -27,7 +27,7 @@ data MonomorphicSum = A Int | B Bool deriving Generic data MonomorphicBoth = Both1 Char | Both2 Int Bool deriving Generic data PolyProduct a b c = PolyProduct a b c deriving Generic -instance (ProductProfunctor p, DefaultFields' p MonomorphicProduct) +instance (SemiproductProfunctor p, DefaultFields' p MonomorphicProduct) => Default p MonomorphicProduct MonomorphicProduct instance (SumProfunctor p, DefaultFields' p MonomorphicSum) @@ -65,10 +65,10 @@ checkDFMonomorphicSum = (Sub Dict, Sub Dict) checkDCMonomorphicBoth :: DefaultConstraints' p MonomorphicBoth :<=>: - (ProductProfunctor p, SumProfunctor p, Default p Int Int, Default p Bool Bool, Default p Char Char) + (SemiproductProfunctor p, SumProfunctor p, Default p Int Int, Default p Bool Bool, Default p Char Char) checkDCMonomorphicBoth = (Sub Dict, Sub Dict) checkDCPolyProduct :: DefaultConstraints p (PolyProduct a b c) (PolyProduct a' b' c') :<=>: - (ProductProfunctor p, Default p a a', Default p b b', Default p c c') + (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c') checkDCPolyProduct = (Sub Dict, Sub Dict) From d8fc1d1b54e40238a55a39dc074ec088bcf551a4 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 12:23:32 +1000 Subject: [PATCH 02/15] Remove reference to ProductContravariant from doc comment --- Data/Profunctor/Product.hs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Data/Profunctor/Product.hs b/Data/Profunctor/Product.hs index 7eec86cc..5fba0e03 100644 --- a/Data/Profunctor/Product.hs +++ b/Data/Profunctor/Product.hs @@ -66,10 +66,9 @@ import Data.Profunctor.Product.Flatten import Data.Profunctor.Product.Tuples import Data.Profunctor.Product.Tuples.TH (pTns, maxTupleSize, pNs) --- SemiproductProfunctor and ProductContravariant are potentially --- redundant type classes. It seems to me that these are equivalent --- to Profunctor with Applicative, and Contravariant with Monoid --- respectively: +-- Is SemiproductProfunctor potentially a redundant type class? +-- It seems to me that these are equivalent to Profunctor with +-- Applicative, and Contravariant with Monoid respectively: -- -- import Data.Profunctor -- import Control.Applicative hiding (empty) @@ -97,12 +96,12 @@ import Data.Profunctor.Product.Tuples.TH (pTns, maxTupleSize, pNs) -- Profunctor+Applicative or Contravariant+Monoid approach we do not -- have a guarantee that these operations are polymorphic. -- --- Previously I wanted to replace SemiproductProfunctor and --- ProductContravariant entirely. This proved difficult as it is not --- possible to expand the class constraints to require Applicative and --- Monoid respectively. We can't enforce a constraint 'Applicative (p --- a)' where 'a' does not appear in the head. This seems closely --- related to the above issue of adhoc implementations. +-- Previously I wanted to replace SemiproductProfunctor entirely. +-- This proved difficult as it is not possible to expand the class +-- constraints to require Applicative and Monoid respectively. We +-- can't enforce a constraint 'Applicative (p a)' where 'a' does not +-- appear in the head. This seems closely related to the above issue +-- of adhoc implementations. -- -- There is a potential method of working around this issue using the -- 'constraints' package: From a4603330d888ce3666f0c999a7d37aea38faa5b0 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 12:32:05 +1000 Subject: [PATCH 03/15] Reintroduce `ProductProfunctor` class It represents "`SemiProductProfunctor` with a unit". --- CHANGELOG.md | 3 ++- Data/Profunctor/Product/Class.hs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082d457e..f3b7622c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased (major) -* Rename `ProductProfunctor` to `SemiProductProfunctor`. +* Rename `ProductProfunctor` to `SemiproductProfunctor`: + - The `ProductProfunctor` now means "`SemiproductProfunctor` with a unit" # 0.11.1.1 diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index 6e1caf6f..bb2f433f 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -109,6 +109,22 @@ class Profunctor p => SemiproductProfunctor p where f ***! g = (,) `Profunctor.rmap` Profunctor.lmap fst f **** Profunctor.lmap snd g +class SemiproductProfunctor p => ProductProfunctor p where + -- | Unit for @('***!')@. + unitP :: p () () + unitP = pureP () + + -- | Analogue to 'pure'. + pureP :: a -> p x a + pureP a = Profunctor.dimap (const ()) (const a) unitP + + -- | Analogue to 'Data.Functor.Contravariant.Divisible.conquer' + -- (from "contravariant"). The 'Monoid' constraint is necessary to + -- provide a "default" value to emit. + conquerP :: Monoid x => p a x + conquerP = pureP mempty + {-# MINIMAL unitP | pureP #-} + class Profunctor p => SumProfunctor p where -- Morally we should have 'zero :: p Void Void' but I don't think -- that would actually be useful From 3d9630112897d7a2b81ba314b587217ca148e9e4 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 12:46:02 +1000 Subject: [PATCH 04/15] Add manual `ProductProfunctor` instances --- Data/Profunctor/Product.hs | 49 ++++++++++++++++++------ Data/Profunctor/Product/Default/Class.hs | 4 +- Data/Profunctor/Product/Examples.hs | 18 ++++++--- Test/Definitions.hs | 4 +- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/Data/Profunctor/Product.hs b/Data/Profunctor/Product.hs index 5fba0e03..df2c3225 100644 --- a/Data/Profunctor/Product.hs +++ b/Data/Profunctor/Product.hs @@ -47,7 +47,7 @@ import qualified Data.Profunctor as Profunctor import Data.Profunctor.Composition (Procompose(..)) import Data.Functor.Contravariant.Divisible (Divisible(..), Decidable, chosen) import Control.Category (id) -import Control.Arrow (Arrow, (***), ArrowChoice, (+++)) +import Control.Arrow (Arrow(arr), (***), ArrowChoice, (+++)) import Control.Applicative (Applicative, liftA2, pure, (<*>), Alternative, (<|>), (<$>)) import Data.Monoid (Monoid, mempty) @@ -122,23 +122,30 @@ import Data.Profunctor.Product.Tuples.TH (pTns, maxTupleSize, pNs) (***$) = Profunctor.rmap instance SemiproductProfunctor (->) where - purePP = pure (****) = (<*>) +instance ProductProfunctor (->) where + pureP = pure + instance Arrow arr => SemiproductProfunctor (WrappedArrow arr) where - empty = id (***!) = (***) +instance Arrow arr => ProductProfunctor (WrappedArrow arr) where + unitP = arr id + instance SemiproductProfunctor Tagged where - purePP = pure (****) = (<*>) +instance ProductProfunctor Tagged where + pureP = pure + instance Applicative f => SemiproductProfunctor (Star f) where - purePP = pure (****) = (<*>) +instance Applicative f => ProductProfunctor (Star f) where + pureP = pure + instance Functor f => SemiproductProfunctor (Costar f) where - purePP = pure (****) = (<*>) -- | @since 0.11.1.0 @@ -146,31 +153,49 @@ instance Monoid r => SemiproductProfunctor (Forget r) where purePP _ = Forget (const mempty) Forget f ***! Forget g = Forget $ \(a, a') -> f a <> g a' +instance Monoid r => ProductProfunctor (Forget r) where + unitP = Forget $ const mempty + +instance Functor f => ProductProfunctor (Costar f) where + pureP = pure + instance (SemiproductProfunctor p, SemiproductProfunctor q) => SemiproductProfunctor (Procompose p q) where - purePP a = Procompose (purePP a) (purePP ()) Procompose pf qf **** Procompose pa qa = Procompose (lmap fst pf **** lmap snd pa) ((,) ***$ qf **** qa) +instance (ProductProfunctor p, ProductProfunctor q) => ProductProfunctor (Procompose p q) where + pureP a = Procompose (pureP a) (pureP ()) + instance (Functor f, Applicative g, SemiproductProfunctor p) => SemiproductProfunctor (Biff p f g) where - purePP = Biff . purePP . pure Biff abc **** Biff ab = Biff $ (<*>) ***$ abc **** ab +instance (Functor f, Applicative g, ProductProfunctor p) => ProductProfunctor (Biff p f g) where + pureP = Biff . pureP . pure + instance Applicative f => SemiproductProfunctor (Joker f) where - purePP = Joker . pure Joker bc **** Joker b = Joker $ bc <*> b +instance Applicative f => ProductProfunctor (Joker f) where + pureP = Joker . pure + instance Divisible f => SemiproductProfunctor (Clown f) where - purePP _ = Clown conquer Clown l **** Clown r = Clown $ divide (\a -> (a, a)) l r +instance Divisible f => ProductProfunctor (Clown f) where + pureP _ = Clown conquer + instance (SemiproductProfunctor p, SemiproductProfunctor q) => SemiproductProfunctor (Product p q) where - purePP a = Pair (purePP a) (purePP a) Pair l1 l2 **** Pair r1 r2 = Pair (l1 **** r1) (l2 **** r2) +instance (ProductProfunctor p, ProductProfunctor q) => ProductProfunctor (Product p q) where + pureP a = Pair (pureP a) (pureP a) + instance (Applicative f, SemiproductProfunctor p) => SemiproductProfunctor (Tannen f p) where - purePP = Tannen . pure . purePP Tannen f **** Tannen a = Tannen $ liftA2 (****) f a +instance (Applicative f, ProductProfunctor p) => ProductProfunctor (Tannen f p) where + pureP = Tannen . pure . pureP + -- { Sum instance SumProfunctor (->) where diff --git a/Data/Profunctor/Product/Default/Class.hs b/Data/Profunctor/Product/Default/Class.hs index 19a79fd6..5e399081 100644 --- a/Data/Profunctor/Product/Default/Class.hs +++ b/Data/Profunctor/Product/Default/Class.hs @@ -74,9 +74,9 @@ class GDefault p f g where type GDefCnstr p f g :: Constraint gdef1 :: p (f a) (g a) -instance SemiproductProfunctor p => GDefault p U1 U1 where +instance ProductProfunctor p => GDefault p U1 U1 where type GDefCnstr p U1 U1 = () - gdef1 = dimap (const ()) (const U1) empty + gdef1 = dimap (const ()) (const U1) unitP instance (Profunctor p, GDefault p f g) => GDefault p (M1 i c f) (M1 i c g) where type GDefCnstr p (M1 i c f) (M1 i c g) = GDefCnstr p f g diff --git a/Data/Profunctor/Product/Examples.hs b/Data/Profunctor/Product/Examples.hs index 7842cdda..fa868f01 100644 --- a/Data/Profunctor/Product/Examples.hs +++ b/Data/Profunctor/Product/Examples.hs @@ -48,10 +48,12 @@ instance Applicative f => Applicative (Replicator r f a) where instance Functor f => P.Profunctor (Replicator r f) where dimap _ h (Replicator f) = Replicator ((fmap . fmap) h f) -instance Applicative f=> PP.SemiproductProfunctor (Replicator r f) where - purePP = pure +instance Applicative f => PP.SemiproductProfunctor (Replicator r f) where (****) = (<*>) +instance Applicative f => PP.ProductProfunctor (Replicator r f) where + pureP = pure + -- In the real world this would be 'StateT [a] Maybe b' but I don't want to -- pick up the transformers dependency here newtype Take a z b = Take ([a] -> Maybe ([a], b)) @@ -94,9 +96,11 @@ instance P.Profunctor (Take a) where dimap _ g (Take h) = Take ((fmap . fmap . fmap) g h) instance PP.SemiproductProfunctor (Take a) where - purePP = pure (****) = (<*>) +instance PP.ProductProfunctor (Take a) where + pureP = pure + newtype Traverse f a b = Traverse { runTraverse :: a -> f b } deriving Functor -- | Use 'sequenceT' instead. It has a better name. @@ -133,9 +137,11 @@ instance Functor f => P.Profunctor (Traverse f) where dimap g h (Traverse f) = Traverse (P.dimap g (fmap h) f) instance Applicative f => PP.SemiproductProfunctor (Traverse f) where - purePP = pure (****) = (<*>) +instance Applicative f => PP.ProductProfunctor (Traverse f) where + pureP = pure + newtype Zipper a b = Zipper { unZipper :: Traverse ZipList a b } deriving Functor @@ -152,9 +158,11 @@ instance Applicative (Zipper a) where f <*> x = Zipper ((<*>) (unZipper f) (unZipper x)) instance PP.SemiproductProfunctor Zipper where - purePP = pure (****) = (<*>) +instance PP.ProductProfunctor Zipper where + pureP = pure + -- } -- | A challenge from a Clojurist on Hacker News diff --git a/Test/Definitions.hs b/Test/Definitions.hs index 203fc212..b01a5ed3 100644 --- a/Test/Definitions.hs +++ b/Test/Definitions.hs @@ -72,9 +72,11 @@ instance Profunctor Arrow where dimap f g = Arrow . dimap f g . unArrow instance SemiproductProfunctor Arrow where - purePP = Arrow . purePP f **** g = Arrow (unArrow f **** unArrow g) +instance ProductProfunctor Arrow where + pureP = Arrow . pureP + data Unit = Unit class Pointed a where From 31589d0c7bc1c08735315b744f880e4e0ae39807 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 12:52:32 +1000 Subject: [PATCH 05/15] Rename unital methods on `SemiProductProfunctor` and deprecate old --- CHANGELOG.md | 2 ++ Data/Profunctor/Product.hs | 15 ++++++++------- Data/Profunctor/Product/Class.hs | 27 ++++++++++++--------------- Data/Profunctor/Product/Tuples/TH.hs | 8 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b7622c..7dce8999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ * Rename `ProductProfunctor` to `SemiproductProfunctor`: - The `ProductProfunctor` now means "`SemiproductProfunctor` with a unit" + - Old `ProductProfunctor#empty` has been deprecated; use `ProductProfunctor#unitP` + - Old `ProductProfunctor#purePP` has been deprecated; use `ProductProfunctor#pureP` # 0.11.1.1 diff --git a/Data/Profunctor/Product.hs b/Data/Profunctor/Product.hs index df2c3225..53509ff7 100644 --- a/Data/Profunctor/Product.hs +++ b/Data/Profunctor/Product.hs @@ -41,12 +41,10 @@ module Data.Profunctor.Product (-- * @ProductProfunctor@ module Data.Profunctor.Product.Class, module Data.Profunctor.Product) where -import Prelude hiding (id) import Data.Profunctor (Profunctor, lmap, WrappedArrow, Star(Star), Costar, Forget(Forget)) import qualified Data.Profunctor as Profunctor import Data.Profunctor.Composition (Procompose(..)) import Data.Functor.Contravariant.Divisible (Divisible(..), Decidable, chosen) -import Control.Category (id) import Control.Arrow (Arrow(arr), (***), ArrowChoice, (+++)) import Control.Applicative (Applicative, liftA2, pure, (<*>), Alternative, (<|>), (<$>)) @@ -131,7 +129,7 @@ instance Arrow arr => SemiproductProfunctor (WrappedArrow arr) where (***!) = (***) instance Arrow arr => ProductProfunctor (WrappedArrow arr) where - unitP = arr id + unitP = arr (const ()) instance SemiproductProfunctor Tagged where (****) = (<*>) @@ -149,10 +147,13 @@ instance Functor f => SemiproductProfunctor (Costar f) where (****) = (<*>) -- | @since 0.11.1.0 -instance Monoid r => SemiproductProfunctor (Forget r) where - purePP _ = Forget (const mempty) +-- +-- /Since 0.12.0.0:/ Superclass constraint relaxed from @'Monoid' r@ +-- to @'Semigroup' r@. +instance Semigroup r => SemiproductProfunctor (Forget r) where Forget f ***! Forget g = Forget $ \(a, a') -> f a <> g a' +-- | @since 0.11.1.0 instance Monoid r => ProductProfunctor (Forget r) where unitP = Forget $ const mempty @@ -230,8 +231,8 @@ instance (Applicative f, SumProfunctor p) => SumProfunctor (Tannen f p) where -- in spirit, a generalisation of @traverse :: (a -> f b) -> [a] -> f -- [b]@, but the types need to be shuffled around a bit to make that -- work. -list :: (SemiproductProfunctor p, SumProfunctor p) => p a b -> p [a] [b] -list p = Profunctor.dimap fromList toList (empty +++! (p ***! list p)) +list :: (ProductProfunctor p, SumProfunctor p) => p a b -> p [a] [b] +list p = Profunctor.dimap fromList toList (unitP +++! (p ***! list p)) where toList :: Either () (a, [a]) -> [a] toList = either (const []) (uncurry (:)) fromList :: [a] -> Either () (a, [a]) diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index bb2f433f..eecc11e3 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -77,15 +77,6 @@ import qualified Data.Profunctor as Profunctor -- ('****') = ('Control.Applicative.<*>') -- @ class Profunctor p => SemiproductProfunctor p where - -- | 'purePP' is the generalisation of @Applicative@'s - -- 'Control.Applicative.pure'. - -- - -- (You probably won't need to use this except to define - -- 'SemiproductProfunctor' instances. In your own code @pure@ should be - -- sufficient.) - purePP :: b -> p a b - purePP b = Profunctor.dimap (const ()) (const b) empty - -- | '****' is the generalisation of @Applicative@'s -- 'Control.Applicative.<*>'. -- @@ -96,11 +87,6 @@ class Profunctor p => SemiproductProfunctor p where (****) f x = Profunctor.dimap dup (uncurry ($)) (f ***! x) where dup y = (y, y) - -- | Use @pure ()@ instead. @empty@ may be deprecated in a future - -- version. - empty :: p () () - empty = purePP () - -- | Use @\\f g -> (,) 'Control.Applicative.<$>' -- 'Data.Profunctor.lmap' fst f 'Control.Applicative.<*>' -- 'Data.Profunctor.lmap' snd g@ instead. @@ -111,7 +97,7 @@ class Profunctor p => SemiproductProfunctor p where class SemiproductProfunctor p => ProductProfunctor p where -- | Unit for @('***!')@. - unitP :: p () () + unitP :: p x () unitP = pureP () -- | Analogue to 'pure'. @@ -125,6 +111,17 @@ class SemiproductProfunctor p => ProductProfunctor p where conquerP = pureP mempty {-# MINIMAL unitP | pureP #-} + -- | Deprecated alias for 'unitP'. Will be removed in a future version. + empty :: p () () + empty = unitP + + -- | Deprecated alias for 'pureP'. Will be removed in a future version. + purePP :: a -> p x a + purePP = pureP + +{-# DEPRECATED empty "use unitP" #-} +{-# DEPRECATED purePP "use pureP"#-} + class Profunctor p => SumProfunctor p where -- Morally we should have 'zero :: p Void Void' but I don't think -- that would actually be useful diff --git a/Data/Profunctor/Product/Tuples/TH.hs b/Data/Profunctor/Product/Tuples/TH.hs index bf250897..24d97193 100644 --- a/Data/Profunctor/Product/Tuples/TH.hs +++ b/Data/Profunctor/Product/Tuples/TH.hs @@ -13,7 +13,7 @@ import Language.Haskell.TH import Language.Haskell.TH.Datatype.TyVarBndr import Data.Profunctor (Profunctor (dimap)) -import Data.Profunctor.Product.Class (SemiproductProfunctor, (***!), empty) +import Data.Profunctor.Product.Class (ProductProfunctor, SemiproductProfunctor, (***!), unitP) import Data.Profunctor.Product.Default.Class (Default (def)) import Control.Applicative (pure) @@ -40,7 +40,7 @@ pTns :: [Int] -> Q [Dec] pTns = fmap concat . mapM pTn productProfunctor :: Name -> Q Pred -productProfunctor p = [t|SemiproductProfunctor $(v p)|] +productProfunctor p = [t|ProductProfunctor $(v p)|] where v = pure . VarT default_ :: Name -> Name -> Name -> Q Pred @@ -61,7 +61,7 @@ pTn n = sequence [sig, fun] `appT` foldl appT (conT tN) (map varT . take n $ bs) fun = funD (pT n) [ clause [] (normalB bdy) [] ] bdy = case n of - 0 -> [| const empty |] + 0 -> [| const unitP |] 1 -> [| id |] 2 -> [| uncurry (***!) |] _ -> [| chain $(varE (pT (n - 1))) |] @@ -174,7 +174,7 @@ mkDefaultN n = mkTupT = foldl appT (tupleT n) . map varT mkFun = funD 'def [clause [] bdy []] bdy = normalB $ case n of - 0 -> varE 'empty + 0 -> varE 'unitP _ -> varE (mkName $ 'p':show n) `appE` tupE (replicate n [| def |]) p = mkName "p" x = varT (mkName "x") From f756736905e2a13cd74762f0fca26ab5f060750a Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 13:00:07 +1000 Subject: [PATCH 06/15] Reorganise methods on `SemiProductProfunctor` This places all three main ways of viewing this class on equal footing: * The "tupling profunctors" perspective: `p a b -> p a' b' -> p (a, a') (b, b')` * The "applicative output" perspective: `p x (a -> b) -> p x a -> p x b` * The "divisible input" perspective: `(a -> (b, c)) -> p b x -> p c x -> p a x` --- CHANGELOG.md | 4 ++++ Data/Profunctor/Product/Class.hs | 39 ++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dce8999..eae48255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ * Rename `ProductProfunctor` to `SemiproductProfunctor`: - The `ProductProfunctor` now means "`SemiproductProfunctor` with a unit" + - Class methods now capture three perspectives: "`Applicative` + operations on the output" (`(****)`), "`Divisible` operations on + the input" (`diviseP`), and "tupling" (`(***!)`). + - Introduce `ProductProfuntor` to represent "`SemiProductProfunctor` with a unit" - Old `ProductProfunctor#empty` has been deprecated; use `ProductProfunctor#unitP` - Old `ProductProfunctor#purePP` has been deprecated; use `ProductProfunctor#pureP` diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index eecc11e3..bd240898 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -2,6 +2,7 @@ module Data.Profunctor.Product.Class where import Data.Profunctor (Profunctor) import qualified Data.Profunctor as Profunctor +import Data.Semigroup (Semigroup, (<>)) -- | 'ProductProfunctor' is a generalization of -- 'Control.Applicative.Applicative'. @@ -77,23 +78,31 @@ import qualified Data.Profunctor as Profunctor -- ('****') = ('Control.Applicative.<*>') -- @ class Profunctor p => SemiproductProfunctor p where - -- | '****' is the generalisation of @Applicative@'s + (***!) :: p a b -> p a' b' -> p (a, a') (b, b') + f ***! g = liftP2 (,) (Profunctor.lmap fst f) (Profunctor.lmap snd g) + + -- | '****' is the analogue of @Applicative@'s -- 'Control.Applicative.<*>'. - -- - -- (You probably won't need to use this except to define - -- 'SemiproductProfunctor' instances. In your own code @\<*\>@ - -- should be sufficient.) - (****) :: p a (b -> c) -> p a b -> p a c - (****) f x = Profunctor.dimap dup (uncurry ($)) (f ***! x) - where dup y = (y, y) + (****) :: p x (a -> b) -> p x a -> p x b + (****) f a = Profunctor.dimap dup (uncurry ($)) (f ***! a) + where dup x = (x, x) - -- | Use @\\f g -> (,) 'Control.Applicative.<$>' - -- 'Data.Profunctor.lmap' fst f 'Control.Applicative.<*>' - -- 'Data.Profunctor.lmap' snd g@ instead. - -- @(***!)@ may be deprecated in a future version. - (***!) :: p a b -> p a' b' -> p (a, a') (b, b') - f ***! g = (,) `Profunctor.rmap` Profunctor.lmap fst f - **** Profunctor.lmap snd g + -- | Analogue to 'Control.Applicative.liftA2' + liftP2 :: (a -> b -> c) -> p x a -> p x b -> p x c + liftP2 f p q = Profunctor.rmap f p **** q + + -- | Analogue to @divise@ (from "semigroupoids") or + -- 'Data.Functor.Contravariant.Divisible.decide' (from + -- "contravariant"). The 'Semigroup' constraint is necessary to + -- combine the "output" values. + -- + -- Why didn't we need a constraint when writing the analogues to + -- 'Applicative' operations? In the absence of linear types, there + -- are only trivial comonoids; we can always produce a function to + -- "duplicate" the input. + diviseP :: Semigroup x => (a -> (b, c)) -> p b x -> p c x -> p a x + diviseP f p q = Profunctor.dimap f (uncurry (<>)) $ p ***! q + {-# MINIMAL (***!) | (****) | liftP2 #-} class SemiproductProfunctor p => ProductProfunctor p where -- | Unit for @('***!')@. From 0ddc1d8b621aa1509e5e104544f03a80a5d3de75 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 13:02:53 +1000 Subject: [PATCH 07/15] Add `divisedP` as analogue to `divided` from `contravariant` --- CHANGELOG.md | 1 + Data/Profunctor/Product/Class.hs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eae48255..1fe6c08c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Introduce `ProductProfuntor` to represent "`SemiProductProfunctor` with a unit" - Old `ProductProfunctor#empty` has been deprecated; use `ProductProfunctor#unitP` - Old `ProductProfunctor#purePP` has been deprecated; use `ProductProfunctor#pureP` +* Add `divisedP = diviseP id` # 0.11.1.1 diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index bd240898..e6633e71 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -104,6 +104,13 @@ class Profunctor p => SemiproductProfunctor p where diviseP f p q = Profunctor.dimap f (uncurry (<>)) $ p ***! q {-# MINIMAL (***!) | (****) | liftP2 #-} +-- | Analogue to @divised@ from "semigroupoids" or +-- 'Data.Functor.Contravariant.Divisible.divided' from +-- "contravariant". +divisedP :: + (SemiproductProfunctor p, Semigroup x) => p a x -> p b x -> p (a, b) x +divisedP = diviseP id + class SemiproductProfunctor p => ProductProfunctor p where -- | Unit for @('***!')@. unitP :: p x () From 673409ab1a8a6b75786c06d4dff9b20cbe6f0896 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 13:43:55 +1000 Subject: [PATCH 08/15] Add `conqueredP` as analogue to `conquered` from `contravariant` --- CHANGELOG.md | 1 + Data/Profunctor/Product/Class.hs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe6c08c..b3a5f736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Old `ProductProfunctor#empty` has been deprecated; use `ProductProfunctor#unitP` - Old `ProductProfunctor#purePP` has been deprecated; use `ProductProfunctor#pureP` * Add `divisedP = diviseP id` +* Add `conqueredP = conquerP` (redundant, but symmetric with `contravariant` interface) # 0.11.1.1 diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index e6633e71..003c9a3a 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -138,6 +138,11 @@ class SemiproductProfunctor p => ProductProfunctor p where {-# DEPRECATED empty "use unitP" #-} {-# DEPRECATED purePP "use pureP"#-} +-- | As redundant as 'Data.Functor.Contravariant.Divisible.conquered' +-- from "contravariant", but also provided for symmetry. +conqueredP :: (ProductProfunctor p, Monoid x) => p () x +conqueredP = conquerP + class Profunctor p => SumProfunctor p where -- Morally we should have 'zero :: p Void Void' but I don't think -- that would actually be useful From 43d1f05c1ba9553310c7cb7df4f573ead2a552fd Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 13:42:10 +1000 Subject: [PATCH 09/15] Rename `SumProfunctor` to `SemisumProfunctor` --- CHANGELOG.md | 1 + Data/Profunctor/Product.hs | 20 ++++++++++---------- Data/Profunctor/Product/Class.hs | 2 +- Data/Profunctor/Product/Default/Class.hs | 8 ++++---- Test/Definitions.hs | 4 ++-- Test/DefinitionsUndecidable.hs | 6 +++--- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a5f736..ae74b1e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Old `ProductProfunctor#purePP` has been deprecated; use `ProductProfunctor#pureP` * Add `divisedP = diviseP id` * Add `conqueredP = conquerP` (redundant, but symmetric with `contravariant` interface) +* Rename `SumProfunctor` to `SemisumProfunctor` # 0.11.1.1 diff --git a/Data/Profunctor/Product.hs b/Data/Profunctor/Product.hs index 53509ff7..c534cd8a 100644 --- a/Data/Profunctor/Product.hs +++ b/Data/Profunctor/Product.hs @@ -199,39 +199,39 @@ instance (Applicative f, ProductProfunctor p) => ProductProfunctor (Tannen f p) -- { Sum -instance SumProfunctor (->) where +instance SemisumProfunctor (->) where f +++! g = either (Left . f) (Right . g) -instance ArrowChoice arr => SumProfunctor (WrappedArrow arr) where +instance ArrowChoice arr => SemisumProfunctor (WrappedArrow arr) where (+++!) = (+++) -instance Applicative f => SumProfunctor (Star f) where +instance Applicative f => SemisumProfunctor (Star f) where Star f +++! Star g = Star $ either (fmap Left . f) (fmap Right . g) -- | @since 0.11.1.0 -instance SumProfunctor (Forget r) where +instance SemisumProfunctor (Forget r) where Forget f +++! Forget g = Forget $ either f g -instance (SumProfunctor p, SumProfunctor q) => SumProfunctor (Procompose p q) where +instance (SemisumProfunctor p, SemisumProfunctor q) => SemisumProfunctor (Procompose p q) where Procompose pa qa +++! Procompose pb qb = Procompose (pa +++! pb) (qa +++! qb) -instance Alternative f => SumProfunctor (Joker f) where +instance Alternative f => SemisumProfunctor (Joker f) where Joker f +++! Joker g = Joker $ Left <$> f <|> Right <$> g -instance Decidable f => SumProfunctor (Clown f) where +instance Decidable f => SemisumProfunctor (Clown f) where Clown f +++! Clown g = Clown $ chosen f g -instance (SumProfunctor p, SumProfunctor q) => SumProfunctor (Product p q) where +instance (SemisumProfunctor p, SemisumProfunctor q) => SemisumProfunctor (Product p q) where Pair l1 l2 +++! Pair r1 r2 = Pair (l1 +++! r1) (l2 +++! r2) -instance (Applicative f, SumProfunctor p) => SumProfunctor (Tannen f p) where +instance (Applicative f, SemisumProfunctor p) => SemisumProfunctor (Tannen f p) where Tannen l +++! Tannen r = Tannen $ liftA2 (+++!) l r -- | A generalisation of @map :: (a -> b) -> [a] -> [b]@. It is also, -- in spirit, a generalisation of @traverse :: (a -> f b) -> [a] -> f -- [b]@, but the types need to be shuffled around a bit to make that -- work. -list :: (ProductProfunctor p, SumProfunctor p) => p a b -> p [a] [b] +list :: (ProductProfunctor p, SemisumProfunctor p) => p a b -> p [a] [b] list p = Profunctor.dimap fromList toList (unitP +++! (p ***! list p)) where toList :: Either () (a, [a]) -> [a] toList = either (const []) (uncurry (:)) diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index 003c9a3a..bfc34eb2 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -143,7 +143,7 @@ class SemiproductProfunctor p => ProductProfunctor p where conqueredP :: (ProductProfunctor p, Monoid x) => p () x conqueredP = conquerP -class Profunctor p => SumProfunctor p where +class Profunctor p => SemisumProfunctor p where -- Morally we should have 'zero :: p Void Void' but I don't think -- that would actually be useful (+++!) :: p a b -> p a' b' -> p (Either a a') (Either b b') diff --git a/Data/Profunctor/Product/Default/Class.hs b/Data/Profunctor/Product/Default/Class.hs index 5e399081..d12e9855 100644 --- a/Data/Profunctor/Product/Default/Class.hs +++ b/Data/Profunctor/Product/Default/Class.hs @@ -36,12 +36,12 @@ type DefaultFields p a b = GDefCnstr p (Rep a) (Rep b) type DefaultFields' p a = DefaultFields p a a -- | @'DefaultPConstraints' p a@ expands to the minimal combination of --- @'Profunctor' p@, @'SemiproductProfunctor' p@, @'SumProfunctor' p@ needed to implement +-- @'Profunctor' p@, @'SemiproductProfunctor' p@, @'SemisumProfunctor' p@ needed to implement -- the instance @'Default' p a a@ for a 'Generic' datatype @a@. -- -- > DefaultPConstraints p Foo = -- > ( SemiproductProfunctor p -- because Foo has a constructor Bar with many fields --- > , SumProfunctor p -- because Foo has multiple constructors +-- > , SemisumProfunctor p -- because Foo has multiple constructors -- > ) -- -- > DefaultConstraints p (a, b) = @@ -90,7 +90,7 @@ instance (SemiproductProfunctor p, GDefault p f f', GDefault p g g') => GDefault type GDefCnstr p (f :*: g) (f' :*: g') = (GDefCnstr p f f', GDefCnstr p g g') gdef1 = dimap (\(x :*: y) -> (x, y)) (uncurry (:*:)) $ gdef1 ***! gdef1 -instance (SumProfunctor p, GDefault p f f', GDefault p g g') => GDefault p (f :+: g) (f' :+: g') where +instance (SemisumProfunctor p, GDefault p f f', GDefault p g g') => GDefault p (f :+: g) (f' :+: g') where type GDefCnstr p (f :+: g) (f' :+: g') = (GDefCnstr p f f', GDefCnstr p g g') gdef1 = dimap sumToEither eitherToSum $ gdef1 +++! gdef1 where @@ -106,7 +106,7 @@ type instance GDefPCnstr p U1 = SemiproductProfunctor p type instance GDefPCnstr p (M1 i c f) = GDefPCnstr p f type instance GDefPCnstr p (K1 i c) = Profunctor p type instance GDefPCnstr p (f :*: g) = SemiproductProfunctor p -type instance GDefPCnstr p (f :+: g) = (SumProfunctor p, GDefPCnstr p f, GDefPCnstr p g) +type instance GDefPCnstr p (f :+: g) = (SemisumProfunctor p, GDefPCnstr p f, GDefPCnstr p g) gdef :: (Profunctor p, Generic a, Generic b, GDefault p (Rep a) (Rep b)) => p a b gdef = dimap from to gdef1 diff --git a/Test/Definitions.hs b/Test/Definitions.hs index b01a5ed3..b23418bc 100644 --- a/Test/Definitions.hs +++ b/Test/Definitions.hs @@ -54,10 +54,10 @@ instance (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c instance (SemiproductProfunctor p, Default p a a', Default p b b', Default p c c') => Default p (RecordGeneric a b c) (RecordGeneric a' b' c') -instance (SumProfunctor p, Default p a a', Default p b b') +instance (SemisumProfunctor p, Default p a a', Default p b b') => Default p (SumGeneric a b) (SumGeneric a' b') -instance (SemiproductProfunctor p, SumProfunctor p, Default p a a', Default p b b', Default p c c') +instance (SemiproductProfunctor p, SemisumProfunctor p, Default p a a', Default p b b', Default p c c') => Default p (ProductAndSumGeneric a b c) (ProductAndSumGeneric a' b' c') data Data2Inferrable a b = Data2Inferrable a b diff --git a/Test/DefinitionsUndecidable.hs b/Test/DefinitionsUndecidable.hs index f078b006..b6259e00 100644 --- a/Test/DefinitionsUndecidable.hs +++ b/Test/DefinitionsUndecidable.hs @@ -18,7 +18,7 @@ module DefinitionsUndecidable where -- It's a bit sad that these need UndecidableInstances import GHC.Generics (Generic) -import Data.Profunctor.Product (SemiproductProfunctor, SumProfunctor) +import Data.Profunctor.Product (SemiproductProfunctor, SemisumProfunctor) import Data.Profunctor.Product.Default (Default, DefaultFields', DefaultConstraints, DefaultConstraints') @@ -30,7 +30,7 @@ data PolyProduct a b c = PolyProduct a b c deriving Generic instance (SemiproductProfunctor p, DefaultFields' p MonomorphicProduct) => Default p MonomorphicProduct MonomorphicProduct -instance (SumProfunctor p, DefaultFields' p MonomorphicSum) +instance (SemisumProfunctor p, DefaultFields' p MonomorphicSum) => Default p MonomorphicSum MonomorphicSum instance (DefaultConstraints' p MonomorphicBoth) @@ -65,7 +65,7 @@ checkDFMonomorphicSum = (Sub Dict, Sub Dict) checkDCMonomorphicBoth :: DefaultConstraints' p MonomorphicBoth :<=>: - (SemiproductProfunctor p, SumProfunctor p, Default p Int Int, Default p Bool Bool, Default p Char Char) + (SemiproductProfunctor p, SemisumProfunctor p, Default p Int Int, Default p Bool Bool, Default p Char Char) checkDCMonomorphicBoth = (Sub Dict, Sub Dict) checkDCPolyProduct From 917972ed40f33a3bdbffc7362d8332a18fa41b2e Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 13:50:12 +1000 Subject: [PATCH 10/15] Reintroduce `SumProfunctor` --- CHANGELOG.md | 3 ++- Data/Profunctor/Product.hs | 31 ++++++++++++++++++++++++++++++- Data/Profunctor/Product/Class.hs | 29 +++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae74b1e3..5cc2833a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ - Old `ProductProfunctor#purePP` has been deprecated; use `ProductProfunctor#pureP` * Add `divisedP = diviseP id` * Add `conqueredP = conquerP` (redundant, but symmetric with `contravariant` interface) -* Rename `SumProfunctor` to `SemisumProfunctor` +* Rename `SumProfunctor` to `SemisumProfunctor`: + - The `SumProfunctor` class now means "`SemisumProfunctor` with a unit" # 0.11.1.1 diff --git a/Data/Profunctor/Product.hs b/Data/Profunctor/Product.hs index c534cd8a..ebceff45 100644 --- a/Data/Profunctor/Product.hs +++ b/Data/Profunctor/Product.hs @@ -44,12 +44,14 @@ module Data.Profunctor.Product (-- * @ProductProfunctor@ import Data.Profunctor (Profunctor, lmap, WrappedArrow, Star(Star), Costar, Forget(Forget)) import qualified Data.Profunctor as Profunctor import Data.Profunctor.Composition (Procompose(..)) -import Data.Functor.Contravariant.Divisible (Divisible(..), Decidable, chosen) +import Data.Functor.Contravariant.Divisible (Divisible(..), Decidable, chosen, lost) import Control.Arrow (Arrow(arr), (***), ArrowChoice, (+++)) import Control.Applicative (Applicative, liftA2, pure, (<*>), Alternative, (<|>), (<$>)) +import qualified Control.Applicative as Applicative import Data.Monoid (Monoid, mempty) import Data.Tagged +import Data.Void (absurd) import Data.Bifunctor.Biff import Data.Bifunctor.Clown @@ -202,31 +204,58 @@ instance (Applicative f, ProductProfunctor p) => ProductProfunctor (Tannen f p) instance SemisumProfunctor (->) where f +++! g = either (Left . f) (Right . g) +instance SumProfunctor (->) where + voidP = absurd + instance ArrowChoice arr => SemisumProfunctor (WrappedArrow arr) where (+++!) = (+++) +instance ArrowChoice arr => SumProfunctor (WrappedArrow arr) where + voidP = arr absurd + instance Applicative f => SemisumProfunctor (Star f) where Star f +++! Star g = Star $ either (fmap Left . f) (fmap Right . g) +instance Applicative f => SumProfunctor (Star f) where + voidP = Star absurd + -- | @since 0.11.1.0 instance SemisumProfunctor (Forget r) where Forget f +++! Forget g = Forget $ either f g +instance SumProfunctor (Forget r) where + voidP = Forget absurd + instance (SemisumProfunctor p, SemisumProfunctor q) => SemisumProfunctor (Procompose p q) where Procompose pa qa +++! Procompose pb qb = Procompose (pa +++! pb) (qa +++! qb) +instance (SumProfunctor p, SumProfunctor q) => SumProfunctor (Procompose p q) where + voidP = Procompose voidP voidP + instance Alternative f => SemisumProfunctor (Joker f) where Joker f +++! Joker g = Joker $ Left <$> f <|> Right <$> g +instance Alternative f => SumProfunctor (Joker f) where + voidP = Joker $ absurd <$> Applicative.empty + instance Decidable f => SemisumProfunctor (Clown f) where Clown f +++! Clown g = Clown $ chosen f g +instance Decidable f => SumProfunctor (Clown f) where + voidP = Clown lost + instance (SemisumProfunctor p, SemisumProfunctor q) => SemisumProfunctor (Product p q) where Pair l1 l2 +++! Pair r1 r2 = Pair (l1 +++! r1) (l2 +++! r2) +instance (SumProfunctor p, SumProfunctor q) => SumProfunctor (Product p q) where + voidP = Pair voidP voidP + instance (Applicative f, SemisumProfunctor p) => SemisumProfunctor (Tannen f p) where Tannen l +++! Tannen r = Tannen $ liftA2 (+++!) l r +instance (Applicative f, SumProfunctor p) => SumProfunctor (Tannen f p) where + voidP = Tannen $ pure voidP + -- | A generalisation of @map :: (a -> b) -> [a] -> [b]@. It is also, -- in spirit, a generalisation of @traverse :: (a -> f b) -> [a] -> f -- [b]@, but the types need to be shuffled around a bit to make that diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index bfc34eb2..6519a0f4 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -3,6 +3,7 @@ module Data.Profunctor.Product.Class where import Data.Profunctor (Profunctor) import qualified Data.Profunctor as Profunctor import Data.Semigroup (Semigroup, (<>)) +import Data.Void (Void, absurd) -- | 'ProductProfunctor' is a generalization of -- 'Control.Applicative.Applicative'. @@ -144,6 +145,30 @@ conqueredP :: (ProductProfunctor p, Monoid x) => p () x conqueredP = conquerP class Profunctor p => SemisumProfunctor p where - -- Morally we should have 'zero :: p Void Void' but I don't think - -- that would actually be useful (+++!) :: p a b -> p a' b' -> p (Either a a') (Either b b') + +-- | 'SemisumProfunctor's with a unit. +-- +-- You can often write these instances mechanically: +-- +-- @ +-- instance SumProfunctor P where +-- concludeP f = 'Data.Bifunctor.Flip.runFlip' $ conclude f -- If you have `instance Conclude ('Data.Bifunctor.Flip.Flip' P a)` +-- concludeP f = 'Data.Bifunctor.Flip.runFlip' $ 'Data.Functor.Contravariant.Divisible.lose' f -- If you have `instance 'Data.Functor.Contravariant.Divisible.Decidable' ('Data.Bifunctor.Flip.Flip' P a)` +-- @ +-- +-- Law: @voidP@ is an identity for @('+++!')@, up to @Either@ +-- rearrangement. +class SemisumProfunctor p => SumProfunctor p where + -- | Unit for @('+++!')@. + voidP :: p Void x + voidP = concludeP id + + -- | Analogue to @conclude@ (from "semigroupoids") or + -- 'Data.Functor.Contravariant.Divisible.lose' (from + -- "contravariant"). + -- + -- Unit for 'decideP'. + concludeP :: (a -> Void) -> p a x + concludeP f = Profunctor.dimap f absurd voidP + {-# MINIMAL voidP | concludeP #-} From cc0691bc1abaa4b3ed6c3dbb264d1e750a136474 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 14:18:56 +1000 Subject: [PATCH 11/15] Add `decideP` to `SemisumProfunctor` --- CHANGELOG.md | 2 + Data/Profunctor/Product/Class.hs | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc2833a..7ad9a0cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * Add `conqueredP = conquerP` (redundant, but symmetric with `contravariant` interface) * Rename `SumProfunctor` to `SemisumProfunctor`: - The `SumProfunctor` class now means "`SemisumProfunctor` with a unit" + - Class methods now capture two perspectives: "`Decidable` + operations on the input (`decideP`)" and "eithering" (`(+++!)`). # 0.11.1.1 diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index 6519a0f4..a7b3e4df 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -144,8 +144,71 @@ class SemiproductProfunctor p => ProductProfunctor p where conqueredP :: (ProductProfunctor p, Monoid x) => p () x conqueredP = conquerP +-- | A 'SemisumProfunctor' is a profunctor whose input and output +-- values can be combined with 'Either'. +-- +-- You can often write these instances mechanically: +-- +-- @ +-- instance SemisumProfunctor P where +-- decideP f p q = 'Data.Bifunctor.Flip.runFlip' $ decide f ('Data.Bifunctor.Flip.Flip' p) ('Data.Bifunctor.Flip.Flip' q) -- If you have `instance Decide (Flip P a)` +-- decideP f p q = 'Data.Bifunctor.Flip.runFlip' $ 'Data.Functor.Contravariant.Divisible.choose' f ('Data.Bifunctor.Flip.Flip' p) ('Data.Bifunctor.Flip.Flip' q) -- If you have `instance 'Data.Functor.Contravariant.Divisible.Decidable' (Flip P a)` +-- @ +-- +-- Laws: +-- +-- * @('+++!')@ is associative up to 'Either' rearrangement. +-- +-- * If @p@ is also a 'SemiproductProfunctor', @('***!')@ should +-- distribute over @('+++!')@ up to tuple/'Either' rearrangement. +-- +-- === Where is the 'Control.Applicative.Alternative' analogue? +-- +-- It is possible to write a version of @('Control.Applicative.<|>')@ +-- that uses 'Either's: +-- +-- @ +-- alt :: 'Control.Applicative.Alternative' f => f a -> f b -> f ('Either' a b) +-- alt f g = Left \<$\> f 'Control.Applicative.<|>' Right \<$\> g +-- @ +-- +-- From this, you might expect 'SemisumProfunctor' to contain an +-- analogue to @('Control.Applicative.<|>')@ like this: +-- +-- @ +-- (\<|||!\>) :: 'SemisumProfunctor' p => p x a -> p x a -> p x a +-- p \<|||!\> q = dimap _____ (either id id) $ p '+++!' q +-- ??? +-- @ +-- +-- The type of that hole is @x -> 'Either' x x@, and we cannot choose +-- a sensible default. We also cannot introduce a typeclass like we +-- did with 'diviseP', as this would require us to create a strange +-- comonoid class with 'Either' as the bifunctor: +-- +-- @ +-- -- Yuck: +-- class ComonoidE w where +-- comappendE :: w -> Either w w +-- comemptyE :: w -> 'Void' +-- @ +-- +-- There are no @ComonoidE@ instances because we can never define a +-- @comemptyE@: 'Void' is uninhabited. Even if we restrict ourselves +-- to a @Cosemigroup@ class, it seems unlikely that it will have any +-- useful instances, so we do not bother defining an +-- 'Control.Applicative.Alternative'-style interface from a +-- 'SemisumProfunctor'. class Profunctor p => SemisumProfunctor p where (+++!) :: p a b -> p a' b' -> p (Either a a') (Either b b') + (+++!) p q = decideP id (Profunctor.rmap Left p) (Profunctor.rmap Right q) + + -- | Analogue to @decide@ (from "semigroupoids") or + -- 'Data.Functor.Contravariant.Divisible.choose' (from + -- "contravariant"). + decideP :: (a -> Either b c) -> p b x -> p c x -> p a x + decideP f p q = Profunctor.dimap f (either id id) $ p +++! q + {-# MINIMAL (+++!) | decideP #-} -- | 'SemisumProfunctor's with a unit. -- From 562e8424dbbf904829e8db2ab728e858a189ce6b Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 14:25:41 +1000 Subject: [PATCH 12/15] Add `decidedP` as analogue to `decided` from `semigroupoids` --- CHANGELOG.md | 1 + Data/Profunctor/Product/Class.hs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad9a0cc..eccafaaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - The `SumProfunctor` class now means "`SemisumProfunctor` with a unit" - Class methods now capture two perspectives: "`Decidable` operations on the input (`decideP`)" and "eithering" (`(+++!)`). +* Add `decidedP = decideP id` # 0.11.1.1 diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index a7b3e4df..171553e3 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -210,6 +210,12 @@ class Profunctor p => SemisumProfunctor p where decideP f p q = Profunctor.dimap f (either id id) $ p +++! q {-# MINIMAL (+++!) | decideP #-} +-- | Analogue to @decided@ (from "semigroupoids") or +-- 'Data.Functor.Contravariant.Divisible.chosen' (from +-- "contravariant"). +decidedP :: SemisumProfunctor p => p b x -> p c x -> p (Either b c) x +decidedP = decideP id + -- | 'SemisumProfunctor's with a unit. -- -- You can often write these instances mechanically: From 48c43e60bb8343b602dfb5705b202464fc68a6db Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 14:26:34 +1000 Subject: [PATCH 13/15] Add `concludedP` as analogue to `conclude` from `semigroupoids` --- CHANGELOG.md | 1 + Data/Profunctor/Product/Class.hs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eccafaaf..d0f99fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Class methods now capture two perspectives: "`Decidable` operations on the input (`decideP`)" and "eithering" (`(+++!)`). * Add `decidedP = decideP id` +* Add `concludedP = concludeP id` # 0.11.1.1 diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index 171553e3..5c3d21ec 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -241,3 +241,10 @@ class SemisumProfunctor p => SumProfunctor p where concludeP :: (a -> Void) -> p a x concludeP f = Profunctor.dimap f absurd voidP {-# MINIMAL voidP | concludeP #-} + +-- | Analogue to @concluded@ (from "semigroupoids") or +-- 'Data.Functor.Contravariant.Divisible.lost'. Potentially more +-- meaningful than 'concludeP', as it shows that we definitely cannot +-- receive _anything_ on the input side. +concludedP :: SumProfunctor p => p Void x +concludedP = concludeP id From 9812e734c6d207b1b70bb7ed8433147cdf988082 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 14:31:35 +1000 Subject: [PATCH 14/15] Fix documentation for `SemiproductProfunctor` --- Data/Profunctor/Product/Class.hs | 89 +++++++++++++------------------- 1 file changed, 35 insertions(+), 54 deletions(-) diff --git a/Data/Profunctor/Product/Class.hs b/Data/Profunctor/Product/Class.hs index 5c3d21ec..5471ebb0 100644 --- a/Data/Profunctor/Product/Class.hs +++ b/Data/Profunctor/Product/Class.hs @@ -5,43 +5,16 @@ import qualified Data.Profunctor as Profunctor import Data.Semigroup (Semigroup, (<>)) import Data.Void (Void, absurd) --- | 'ProductProfunctor' is a generalization of --- 'Control.Applicative.Applicative'. --- It has the usual 'Control.Applicative.Applicative' "output" --- (covariant) parameter on the right. Additionally it has an "input" --- (contravariant) type parameter on the left. --- --- The methods for 'ProductProfunctor' correspond closely to those for --- 'Control.Applicative.Applicative' as laid out in the following --- table. --- The only difference between them is that the 'ProductProfunctor' --- has a contravariant type parameter on the left. We can use the --- contravariant to compose them in nice ways as described at --- "Data.Profunctor.Product". --- --- @ --- | Correspondence between Applicative and ProductProfunctor --- | --- | 'Control.Applicative.Applicative' f 'ProductProfunctor' p --- | --- | 'Control.Applicative.pure' 'purePP' --- | :: b -> f b :: b -> p a b --- | --- | ('Control.Applicative.<$>') ('Data.Profunctor.Product.***$') --- | :: (b -> b') :: (b -> b') --- | -> f b -> p a b --- | -> f b' -> p a b' --- | --- | ('Control.Applicative.<*>') ('****') --- | :: f (b -> b') :: p a (b -> b') --- | -> f b -> p a b --- | -> f b' -> p a b' --- @ --- --- If @p@ is an instance of 'ProductProfunctor' then @p a a'@ --- represents a sort of process for turning @a@s into @a'@s that can --- be "laid out side-by-side" with other values of @p@ to form "wider" --- processes. For example, if I have + +-- | A 'SemiproductProfunctor' is a profunctor whose input and output +-- values can be combined with @(,)@. ('***!') makes this most +-- obvious, though ('****') and 'liftP2' are equivalent in power. +-- +-- A value of type @p a x@ with an @instance 'SemiproductProfunctor' p@ +-- often represents some sort of process for turning @a@s into @x@s +-- that can be "laid out side-by-side" with other similar values of +-- @p@ to form "wider" processes. For example, if I have three such +-- encoders: -- -- @ -- p :: p a x -- a process for turning as into xs @@ -49,35 +22,31 @@ import Data.Void (Void, absurd) -- r :: p c z -- a process for turning cs into zs -- @ -- --- then I can combine them using 'p3' to get +-- I can then combine them using 'p3' to get: -- -- @ --- p3 p q r :: p (a, b, c) (x, y, z) -- -- a process for turning (a, b, c)s into (x, y, z)s +-- p3 p q r :: p (a, b, c) (x, y, z) -- @ -- -- You would typically compose 'ProductProfunctor's using --- 'Profunctors''s 'Profunctor.lmap' and 'Applicative''s 'pure', --- '<$>' / 'fmap' and '<*>'. +-- 'Profunctor'\'s 'lmap', '<$>' \/ 'fmap', and @Apply@ \/ +-- 'Applicative'\'s 'pure' and @\<.\>@ \/ @('<*>')@. -- --- It's easy to make instances of 'ProductProfunctor'. Just make --- instances +-- You can often write these instances mechancially: -- -- @ --- instance 'Profunctor' MyProductProfunctor where --- ... --- --- instance 'Control.Applicative.Applicative' (MyProductProfunctor a) where --- ... +-- instance SemiproductProfunctor P where +-- (****) = (\<.\>) -- If you have `instance Apply (P a)` +-- (****) = ('<*>') -- If you have `instance 'Applicative' (P a)` -- @ -- --- and then write +-- Laws: -- --- @ --- instance 'ProductProfunctor' MyProductProfunctor where --- 'purePP' = 'Control.Applicative.pure' --- ('****') = ('Control.Applicative.<*>') --- @ +-- * @('***!')@ is associative up to tuple rearrangement. +-- +-- * If @p@ is also a 'SemisumProfunctor', @('***!')@ should +-- distribute over @('+++!')@ up to tuple/@Either@ rearrangement. class Profunctor p => SemiproductProfunctor p where (***!) :: p a b -> p a' b' -> p (a, a') (b, b') f ***! g = liftP2 (,) (Profunctor.lmap fst f) (Profunctor.lmap snd g) @@ -112,6 +81,18 @@ divisedP :: (SemiproductProfunctor p, Semigroup x) => p a x -> p b x -> p (a, b) x divisedP = diviseP id +-- | 'SemiproductProfunctor's with a unit. +-- +-- If you have an 'Applicative' instance for @P a@, you can write this +-- instance mechanically: +-- +-- @ +-- instance ProductProfunctor p where +-- pureP = pure +-- @ +-- +-- Law: @unitP@ is an identity for @('***!')@, up to tuple +-- rearrangement. class SemiproductProfunctor p => ProductProfunctor p where -- | Unit for @('***!')@. unitP :: p x () From a8daae997fde44958a789ede187d52ec28b5e810 Mon Sep 17 00:00:00 2001 From: Jack Kelly Date: Mon, 10 Apr 2023 14:42:51 +1000 Subject: [PATCH 15/15] Clean up redundant comments and rewrite module haddock --- Data/Profunctor/Product.hs | 90 +++++++++++++------------------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/Data/Profunctor/Product.hs b/Data/Profunctor/Product.hs index ebceff45..4873e4c0 100644 --- a/Data/Profunctor/Product.hs +++ b/Data/Profunctor/Product.hs @@ -1,27 +1,43 @@ {-# OPTIONS_GHC -Wno-orphans #-} {-# LANGUAGE TemplateHaskell #-} --- | If @p@ is an instance of 'SemiproductProfunctor' then @p a a'@ --- represents a sort of process for turning @a@s into @a'@s that can --- be "laid out side-by-side" with other values of @p@ to form "wider" --- processes. For example, if I have +-- | The classes in this module provide "profunctorial" analogues to +-- the operations from the 'Applicative' (@Apply@), +-- 'Data.Functor.Contravariant.Divisible.Divisible' (@Divise@) and +-- 'Data.Functor.Contravariant.Divisible.Decidable' (@Conclude@) type +-- classes: -- -- @ --- a :: p a a' -- a process for turning as into a's --- b :: p b b' -- a process for turning bs into b's --- c :: p c c' -- a process for turning cs into c's --- @ +-- ('<*>') :: 'Applicative' f => f (a -> b) -> f a -> f b +-- ('****') :: 'SemiproductProfunctor' p => p x (a -> b) -> p x a -> p x b -- --- then I can combine them using 'p3' to get +-- 'Control.Applicative.liftA2' :: 'Applicative' f => (a -> b -> c) -> f a -> f b -> f c +-- 'liftP2' :: 'SemiproductProfunctor' p => (a -> b -> c) -> p x a -> p x b -> p x c -- --- @ --- p3 a b c :: p (a, b, c) (a', b', c') --- -- a process for turning (a, b, c)s into (a', b', c')s +-- pure :: 'Applicative' f => a -> f a +-- 'pureP' :: 'SemiproductProfunctor' p => a -> p x a +-- +-- divide :: 'Data.Functor.Contravariant.Divisible.Divisible' f => (a -> (b, c)) -> f b -> f c -> f a -- From contravariant +-- divise :: Divise f => (a -> (b, c)) -> f b -> f c -> f a -- From semigroupoids +-- 'diviseP' :: ('Semigroup' x, 'SemiproductProfunctor' p) => (a -> (b, c)) -> p a x -> p b x -> p c x +-- +-- conquer :: 'Data.Functor.Contravariant.Divisible.Decidable' f => f a -- From contravariant +-- 'conquerP' :: ('Monoid' x, 'ProductProfunctor' p) => p a x +-- +-- choose :: 'Data.Functor.Contravariant.Divisible.Decidable' f => (a -> 'Either' b c) -> f b -> f c -> f a -- From contravariant +-- decide :: Decide f => (a -> 'Either' b c) -> f b -> f c -> f a -- From semigroupoids +-- 'decideP' :: 'SemisumProfunctor' p => (a -> 'Either' b c) -> p b x -> p c x -> p a x +-- +-- lose :: 'Data.Functor.Contravariant.Divisible.Decidable' f => (a -> 'Void') -> f a -- From contravariant +-- conclude :: Conclude f => (a -> 'Void') -> f a -- From semigroupoids +-- 'concludeP' :: 'SumProfunctor' p => (a -> 'Void') -> p a x -- @ -- --- You would typically compose 'SemiproductProfunctor's using --- 'Profunctors''s 'Profunctor.lmap' and 'Applicative''s 'pure', --- '<$>' / 'fmap' and '<*>'. +-- The @(Semi){Sum,Product}Profunctor@ classes also provide more +-- primitive operations using @Either@ and @(,)@. These can be very +-- useful with the @@ +-- package, which can automatically convert data types that have a +-- 'Generic' instance into Eithers-of-Tuples. module Data.Profunctor.Product (-- * @ProductProfunctor@ SemiproductProfunctor(..), (***$), @@ -66,50 +82,6 @@ import Data.Profunctor.Product.Flatten import Data.Profunctor.Product.Tuples import Data.Profunctor.Product.Tuples.TH (pTns, maxTupleSize, pNs) --- Is SemiproductProfunctor potentially a redundant type class? --- It seems to me that these are equivalent to Profunctor with --- Applicative, and Contravariant with Monoid respectively: --- --- import Data.Profunctor --- import Control.Applicative hiding (empty) --- import Data.Functor.Contravariant --- import Data.Monoid --- --- empty :: (Applicative (p ())) => p () () --- empty = pure () --- --- (***!) :: (Applicative (p (a, a')), Profunctor p) => --- p a b -> p a' b' -> p (a, a') (b, b') --- p ***! p' = (,) <$> lmap fst p <*> lmap snd p' --- --- point :: Monoid (f ()) => f () --- point = mempty --- --- (***<) :: (Monoid (f (a, b)), Contravariant f) => --- f a -> f b -> f (a, b) --- p ***< p' = contramap fst p <> contramap snd p' --- --- --- The only thing that makes me think that they are not *completely* --- redundant is that (***!) and (***<) have to be defined --- polymorphically in the type arguments, whereas if we took the --- Profunctor+Applicative or Contravariant+Monoid approach we do not --- have a guarantee that these operations are polymorphic. --- --- Previously I wanted to replace SemiproductProfunctor entirely. --- This proved difficult as it is not possible to expand the class --- constraints to require Applicative and Monoid respectively. We --- can't enforce a constraint 'Applicative (p a)' where 'a' does not --- appear in the head. This seems closely related to the above issue --- of adhoc implementations. --- --- There is a potential method of working around this issue using the --- 'constraints' package: --- stackoverflow.com/questions/12718268/polymorphic-constraint/12718620 --- --- Still, at least we now have default implementations of the class --- methods, which makes things simpler. - -- | '***$' is the generalisation of 'Functor''s @\<$\>@. -- -- '***$' = 'Profunctor.rmap', just like '<$>' = 'fmap'.