on master help sign in
Optimising prisms
commitff59890e04ea337334bb5e946ce2f44866fbe272 authorMatthew Pickering <matthewtpickering@gmail.com> dateFri 12 Jan 2018, 20:26 (9 years ago) parentc60b4d2
7 files changed+677−22
generic-lens.cabal+1 src/Data/Generics/Internal/Lens.hs+48−5 src/Data/Generics/Sum/Any.hs+3−3 src/Data/Generics/Sum/Constructors.hs+17−2 src/Data/Generics/Sum/Internal/Constructors.hs+11−6 src/Data/Profunctor/Extra.hs+589 test/Spec.hs+8−6
generic-lens.cabal+1
@@ -54,6 +54,7 @@ library
5454 , Data.Generics.Product.Internal.Subtype
5555 , Data.Generics.Product.Internal.Typed
5656 , Data.Generics.Product.Internal.Types
57+ , Data.Profunctor.Extra
5758
5859 build-depends: base >= 4.9 && <= 5.0
5960 , profunctors >= 5.0 && <= 6.0
src/Data/Generics/Internal/Lens.hs+48−5
@@ -21,10 +21,12 @@ import Control.Applicative (Const(..))
2121import Data.Functor.Identity (Identity(..))
2222import Data.Monoid (First (..))
2323import Data.Profunctor (Choice(right'), Profunctor(dimap, rmap))
24+import Data.Profunctor.Extra
2425import Data.Profunctor.Unsafe ((#.), (.#))
2526import Data.Tagged
2627import GHC.Generics ((:*:)(..), (:+:)(..), Generic(..), M1(..), Rep)
2728import Boggle hiding (Dirty)
29+import Debug.Trace
2830
2931-- | Type alias for lens
3032type Lens' s a
@@ -41,6 +43,12 @@ type Traversal' s a
4143type Prism s t a b
4244 = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)
4345
46+type PrismP s t a b
47+ = forall p . (Choice p) => p a b -> p s t
48+
49+type PrismE s t a b
50+ = forall p f. (MRight p, MProfunctor p) => p a b -> p s t
51+
4452type Prism' s a
4553 = Prism s s a a
4654
@@ -50,6 +58,9 @@ type Iso' s a
5058type Iso s t a b
5159 = forall p f. (Profunctor p, Functor f) => p a (f b) -> p s (f t)
5260
61+type IsoE s t a b
62+ = forall p f. (MProfunctor p) => p a b -> p s t
63+
5364-- | Getting
5465(^.) :: s -> ((a -> Const a a) -> s -> Const a s) -> a
5566s ^. l = getConst (l Const s)
@@ -89,6 +100,12 @@ left = prism L1 $ gsum Right (Left . R1)
89100right :: Prism ((a :+: b) x) ((a :+: c) x) (b x) (c x)
90101right = prism R1 $ gsum (Left . L1) Right
91102
103+leftE :: PrismE ((a :+: c) x) ((b :+: c) x) (a x) (b x)
104+leftE = prismE L1 $ gsum Right (Left . R1)
105+
106+rightE :: PrismE ((a :+: b) x) ((a :+: c) x) (b x) (c x)
107+rightE = prismE R1 $ gsum (Left . L1) Right
108+
92109gsum :: (a x -> c) -> (b x -> c) -> ((a :+: b) x) -> c
93110gsum f _ (L1 x) = f x
94111gsum _ g (R1 y) = g y
@@ -97,6 +114,9 @@ combine :: Lens' (s x) a -> Lens' (t x) a -> Lens' ((s :+: t) x) a
97114combine sa _ f (L1 s) = fmap (\a -> L1 (set sa a s)) (f (s ^. sa))
98115combine _ ta f (R1 t) = fmap (\a -> R1 (set ta a t)) (f (t ^. ta))
99116
117+prismE :: (b -> t) -> (s -> Either t a) -> PrismE s t a b
118+prismE bt seta = mdimap (liftFun seta) (EitherFun id bt) . mright
119+
100120prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
101121prism bt seta = dimap seta (either pure (fmap bt)) . right'
102122
@@ -104,11 +124,20 @@ prism bt seta = dimap seta (either pure (fmap bt)) . right'
104124repIso :: (Generic a, Generic b) => Iso a b (Rep a x) (Rep b x)
105125repIso = dimap from (fmap to)
106126
127+repIsoE :: (Generic a, Generic b) => IsoE a b (Rep a x) (Rep b x)
128+repIsoE = ddimap from to
129+
130+
107131-- | 'M1' is just a wrapper around `f p`
108132--mIso :: Iso' (M1 i c f p) (f p)
109133mIso :: Iso (M1 i c f p) (M1 i c g p) (f p) (g p)
110134mIso = dimap unM1 (fmap M1)
111135
136+-- | 'M1' is just a wrapper around `f p`
137+--mIso :: Iso' (M1 i c f p) (f p)
138+mIsoE :: IsoE (M1 i c f p) (M1 i c g p) (f p) (g p)
139+mIsoE = ddimap unM1 M1
140+
112141-- These are specialised versions of the Isos above. On GHC 8.0.2, having
113142-- these functions eta-expanded allows the optimiser to inline these functions.
114143mLens :: Lens (M1 i c f p) (M1 i c g p) (f p) (g p)
@@ -151,6 +180,18 @@ ravel coy f s = inj $ coy (\a -> proj (f a)) s
151180
152181-- Pull dimaps to left and rights to right
153182
183+{-
184+instance Choice p => Profunctor (MergeRight p) where
185+ dimap f g (MergeRight (Dirty pab)) = MergeRight (DMap f g pab)
186+ dimap f g (MergeRight (DMap f' g' pab)) = MergeRight (DMap (f' . f) (g . g') pab)
187+ dimap f g (MergeRight (DRight pab)) = MergeRight (DMap f g (right' pab))
188+
189+instance Choice p => Choice (MergeRight p) where
190+ right' (MergeRight (Dirty pab)) = MergeRight (DRight pab)
191+ right' (MergeRight (DMap f g pab)) = MergeRight (DMap (fmap f) (fmap g) (right' pab))
192+ right' (MergeRight (DRight pab)) = MergeRight (DMap assoc' assoc (right' pab))
193+ -}
194+{-
154195data PrismBoggle p a b where
155196 DRight :: (p x y) -> PrismBoggle p (Either z x) (Either z y)
156197 DMap :: (c -> a) -> (b -> d) -> (p a b) -> PrismBoggle p c d
@@ -184,17 +225,19 @@ projPrism :: p a b -> PrismBoggle p a b
184225projPrism pab = Dirty pab
185226
186227--rightFuse :: Choice p => p a b -> p (Either c (Either d a)) (Either c (Either d b))
187−rightFuse x = injPrism (projPrism (injPrism (right' (right' (right' (projPrism x))))))
228+rightFuse x = injPrism (injPrism (right' (right' (right' (projPrism (projPrism x))))))
188229
189230
190231injPrism :: Choice p => PrismBoggle p a b -> p a b
191232injPrism (Dirty pab) = pab
192233injPrism (DMap f g pab) = dimap f g pab
193234injPrism (DRight pab) = right' pab
235+-}
194236
195−prismRavel :: (Choice p, Applicative f)
196− => (PrismBoggle (PrismBoggle p) a (Boggle f b) -> PrismBoggle (PrismBoggle p) s (Boggle f t))
197− -> (p a (f b) -> p s (f t))
198−prismRavel coy pafb = injPrism (injPrism (dimap id lowerBoggle (coy (dimap id liftBoggle (projPrism (projPrism pafb))))))
237+prismRavel :: (Profunctor p, Choice p) =>
238+ (forall p . (MProfunctor p, MRight p) => p a b -> p s t)
239+ -> p a b -> p s t
240+prismRavel l pab = wrappedProfunctor (ravelFusionStack l (WrappedProfunctor pab))
199241{-# INLINE prismRavel #-}
200242
243+
src/Data/Generics/Sum/Any.hs+3−3
@@ -86,11 +86,11 @@ class AsAny (sel :: k) a s | s sel k -> a where
8686 --
8787 -- >>> duck ^? _As @Age
8888 -- Just 2
89− _As :: Prism' s a
89+ _As :: PrismP s s a a
9090
9191instance AsConstructor ctor s s a a => AsAny ctor a s where
9292 _As = _Ctor @ctor
9393
94−instance AsType a s => AsAny a a s where
95− _As = _Typed @a
94+--instance AsType a s => AsAny a a s where
95+-- _As = _Typed @a
9696
src/Data/Generics/Sum/Constructors.hs+17−2
@@ -9,6 +9,7 @@
99{-# LANGUAGE TypeFamilies #-}
1010{-# LANGUAGE TypeOperators #-}
1111{-# LANGUAGE UndecidableInstances #-}
12+{-# LANGUAGE FlexibleContexts #-}
1213
1314-----------------------------------------------------------------------------
1415-- |
@@ -38,6 +39,7 @@ import Data.Generics.Sum.Internal.Constructors
3839import Data.Kind (Constraint, Type)
3940import GHC.Generics (Generic (Rep))
4041import GHC.TypeLits (Symbol, TypeError, ErrorMessage (..))
42+import Data.Profunctor.Extra
4143
4244-- $setup
4345-- == /Running example:/
@@ -96,7 +98,7 @@ class AsConstructor (ctor :: Symbol) s t a b | ctor s -> a, ctor t -> b where
9698 -- ...
9799 -- ... The type Animal Int does not contain a constructor named "Turtle"
98100 -- ...
99− _Ctor :: Prism s t a b
101+ _Ctor :: PrismP s t a b
100102
101103instance
102104 ( Generic s
@@ -110,9 +112,22 @@ instance
110112 , '(t, b) ~ Infer s' a' a (PickTv a' b)
111113 ) => AsConstructor ctor s t a b where
112114
113− _Ctor eta = prismRavel (repIso . _GCtor @ctor) eta
115+ _Ctor eta = prismRavel (repIsoE . _GCtor @ctor) eta
114116 {-# INLINE _Ctor #-}
115117
118+-- Used for testing the optimisation passes
119+_CtorRaw :: forall ctor s t s' a' a b . ( Generic s
120+ , ErrorUnless ctor s (HasCtorP ctor (Rep s))
121+ , Generic t
122+ , s' ~ Proxied s
123+ , Generic s'
124+ , GAsConstructor' ctor (Rep s) a
125+ , GAsConstructor' ctor (Rep s') a'
126+ , GAsConstructor ctor (Rep s) (Rep t) a b
127+ , '(t, b) ~ Infer s' a' a (PickTv a' b)
128+ ) => PrismE s t a b
129+_CtorRaw = repIsoE . _GCtor @ctor
130+
116131-- See Note [Uncluttering type signatures]
117132instance {-# OVERLAPPING #-} AsConstructor ctor (Void1 a) (Void1 b) a b where
118133 _Ctor = undefined
src/Data/Generics/Sum/Internal/Constructors.hs+11−6
@@ -39,7 +39,7 @@ import GHC.TypeLits (Symbol)
3939-- |As 'AsConstructor' but over generic representations as defined by
4040-- "GHC.Generics".
4141class GAsConstructor (ctor :: Symbol) s t a b | ctor s -> a, ctor t -> b where
42− _GCtor :: Prism (s x) (t x) a b
42+ _GCtor :: PrismE (s x) (t x) a b
4343
4444type GAsConstructor' ctor s a = GAsConstructor ctor s s a a
4545
@@ -50,19 +50,24 @@ instance
5050 , ListTuple b bs
5151 ) => GAsConstructor ctor (M1 C ('MetaCons ctor fixity fields) f) (M1 C ('MetaCons ctor fixity fields) g) a b where
5252
53− _GCtor = prism (M1 . gfromCollection . tupleToList) (Right . listToTuple . gtoCollection . unM1)
53+ _GCtor = prismE (M1 . gfromCollection . tupleToList) (Right . listToTuple . gtoCollection . unM1)
54+ {-# INLINE _GCtor #-}
5455
5556instance GSumAsConstructor ctor (HasCtorP ctor l) l r l' r' a b => GAsConstructor ctor (l :+: r) (l' :+: r') a b where
5657 _GCtor = _GSumCtor @ctor @(HasCtorP ctor l)
58+ {-# INLINE _GCtor #-}
5759
5860instance GAsConstructor ctor f f' a b => GAsConstructor ctor (M1 D meta f) (M1 D meta f') a b where
59− _GCtor = mIso . _GCtor @ctor
61+ _GCtor = mIsoE . _GCtor @ctor
62+ {-# INLINE _GCtor #-}
6063
6164class GSumAsConstructor (ctor :: Symbol) (contains :: Bool) l r l' r' a b | ctor l r -> a, ctor l' r' -> b where
62− _GSumCtor :: Prism ((l :+: r) x) ((l' :+: r') x) a b
65+ _GSumCtor :: PrismE ((l :+: r) x) ((l' :+: r') x) a b
6366
6467instance GAsConstructor ctor l l' a b => GSumAsConstructor ctor 'True l r l' r a b where
65− _GSumCtor = left . _GCtor @ctor
68+ _GSumCtor = leftE . _GCtor @ctor
69+ {-# INLINE _GSumCtor #-}
6670
6771instance GAsConstructor ctor r r' a b => GSumAsConstructor ctor 'False l r l r' a b where
68− _GSumCtor = right . _GCtor @ctor
72+ _GSumCtor = rightE . _GCtor @ctor
73+ {-# INLINE _GSumCtor #-}
src/Data/Profunctor/Extra.hsnew file+589
@@ -0,0 +1,589 @@
1+{-# LANGUAGE GADTs #-}
2+{-# LANGUAGE TypeApplications #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE DeriveGeneric #-}
6+module Data.Profunctor.Extra where
7+
8+import Data.Profunctor
9+import GHC.Generics
10+import Debug.Trace
11+
12+
13+-- Pull dimaps to left and rights to right
14+{-
15+data Boggle p a b where
16+ DRight :: (p x y) -> Boggle p (Either z x) (Either z y)
17+ DMap :: (c -> a) -> (b -> d) -> (p a b) -> Boggle p c d
18+ Dirty :: p a b -> Boggle p a b
19+
20+instance Choice p => Profunctor (Boggle p) where
21+ dimap f g (Dirty pab) = DMap f g pab
22+ dimap f g (DMap f' g' pab) = DMap (f' . f) (g . g') pab
23+ dimap f g (DRight pab) = DMap f g (right' pab)
24+
25+instance Choice p => Choice (Boggle p) where
26+ right' (Dirty pab) = DRight pab
27+ right' (DMap f g pab) = DMap (fmap f) (fmap g) (right' pab)
28+ right' (DRight pab) = DRight (right' pab)
29+
30+lift :: p a b -> Boggle p a b
31+lift pab = Dirty pab
32+
33+
34+lower :: Choice p => Boggle p a b -> p a b
35+lower (Dirty pab) = pab
36+lower (DMap f g pab) = dimap f g pab
37+lower (DRight pab) = right' pab
38+
39+test1 :: Choice p => p Int Int -> p (Either z Int) (Either z Int)
40+test1 pab = right' (dimap (+1) (subtract 1) pab)
41+
42+test1spec :: Choice p => p Int Int -> p (Either z Int) (Either z Int)
43+test1spec = lower . test1 . lift
44+-}
45+
46+
47+------------------------------------------------------------------------------
48+
49+--- This type fuses together uses of `dimap` we want to do this last
50+-- as we first need to rearrange the "Eithers"
51+newtype DimapK p b c = DimapK (forall a d . (Fun a b -> Fun c d -> p a d))
52+
53+liftDimapK :: MProfunctor p => p a b -> DimapK p a b
54+liftDimapK pa = DimapK (\f g -> mdimap f g pa)
55+
56+lowerDimapK :: DimapK p a b -> p a b
57+lowerDimapK (DimapK pa) = pa (liftFun id) (liftFun id)
58+
59+-- Note that here because we use (<.>) we potentially flatten "either"
60+instance MProfunctor (DimapK p) where
61+ mdimap f g (DimapK x) = DimapK (\y z -> x (f <.> y) (z <.> g))
62+ {-# INLINE mdimap #-}
63+
64+fmdimap :: Fun a b -> Fun c d -> DimapK p b c -> p a d
65+fmdimap f g (DimapK x) = x f g
66+
67+------------------------------------------------------------------------------
68+-- Wrapper to ensure that a liftDimapK followed by lowerDimapK doesn't
69+-- introduce a dimap.
70+
71+data DimapK1 p b c = DimapK1 (p b c) (DimapK p b c)
72+ | DirtyDimapK1 (p b c)
73+
74+liftDimapK1 :: MProfunctor p => p a b -> DimapK1 p a b
75+liftDimapK1 pab = DimapK1 pab (liftDimapK pab)
76+
77+lowerDimapK1 :: DimapK1 p a b -> p a b
78+lowerDimapK1 (DimapK1 pab _) = pab
79+lowerDimapK1 (DirtyDimapK1 pab) = pab
80+
81+instance MProfunctor p => MProfunctor (DimapK1 p) where
82+ mdimap f g (DimapK1 _ d) = DimapK1 (fmdimap f g d) (mdimap f g d)
83+ mdimap f g (DirtyDimapK1 pab) = DimapK1 (mdimap f g pab) (mdimap f g (liftDimapK pab))
84+ {-# INLINE mdimap #-}
85+
86+--instance MRight p => MRight (DiMapK1 p) where
87+-- mright (DimapK1
88+instance MRight p => MRight (DimapK1 p) where
89+ mright (DimapK1 f g) = DirtyDimapK1 (mright f)
90+ mright (DirtyDimapK1 pab) = DirtyDimapK1 (mright pab)
91+ {-# INLINE mright #-}
92+
93+-- Should be the identity, works
94+testDimapK1 :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
95+testDimapK1 pab = lowerDimapK1 (liftDimapK1 pab)
96+
97+a, b :: Fun Int Int
98+a = liftFun id
99+b = liftFun id
100+
101+-- Should observe one dimap in the specialised version
102+testDimapK1A :: (MProfunctor p) => p Int Int -> p Int Int
103+testDimapK1A pab = mdimap a b (mdimap a b pab)
104+
105+ravelDimapK1 :: (MProfunctor p)
106+ => (DimapK1 p a b -> DimapK1 p c d)
107+ -> p a b -> p c d
108+ravelDimapK1 l pab = lowerDimapK1 (l (liftDimapK1 pab))
109+
110+-- As expected
111+specDimapK1A :: (MProfunctor p) => p Int Int -> p Int Int
112+specDimapK1A = ravelDimapK1 testDimapK1A
113+
114+
115+
116+
117+-------------------------------------------------------------------------------
118+-- Pull Dimaps to the left
119+
120+data DimapSyntax p c d where
121+ DMSMap :: Fun c a -> Fun b d -> p a b -> DimapSyntax p c d
122+ DMSDirty :: p a b -> DimapSyntax p a b
123+
124+liftDimapSyntax :: p a b -> DimapSyntax p a b
125+liftDimapSyntax pab = DMSDirty pab
126+
127+lowerDimapSyntax :: MProfunctor p => DimapSyntax p a b -> p a b
128+lowerDimapSyntax (DMSDirty pab) = pab
129+lowerDimapSyntax (DMSMap pca pbd pab) = mdimap pca pbd pab
130+
131+instance MRight p => MRight (DimapSyntax p) where
132+ mright (DMSDirty pab) = DMSDirty (mright pab)
133+ mright (DMSMap fca fbd pab) = DMSMap (femap fca) (femap fbd) (mright pab)
134+ {-# INLINE mright #-}
135+
136+-- Don't fuse here, we do that in DimapK1 at the end
137+instance MProfunctor p => MProfunctor (DimapSyntax p) where
138+ mdimap f g (DMSDirty p) = DMSMap f g p
139+ mdimap f g (DMSMap f' g' p) = DMSMap (f' <.> f) (g <.> g') p
140+ {-# INLINE mdimap #-}
141+
142+f :: Fun Int (Either Int Int)
143+f = undefined
144+g :: Fun (Either Int Int) Int
145+g = undefined
146+
147+
148+testDimapSyntax :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
149+testDimapSyntax pab = mdimap f g (mright (mdimap f g (mright pab)))
150+
151+ravelDimapSyntax :: (MProfunctor p, MRight p) =>
152+ (DimapSyntax p a b -> DimapSyntax p s t)
153+ -> p a b -> p s t
154+ravelDimapSyntax l pab = lowerDimapSyntax (l (liftDimapSyntax pab))
155+
156+ravelDimapSyntaxFF :: (MProfunctor p, MRight p) =>
157+ (DimapSyntax (RightFuse (RightFreeTheorem p)) a b -> DimapSyntax (RightFuse (RightFreeTheorem p)) s t)
158+ -> p a b -> p s t
159+ravelDimapSyntaxFF l pab = lowerRightFreeTheorem (lowerRightFuse (lowerDimapSyntax (l (liftDimapSyntax (liftRightFuse (liftRightFreeTheorem pab))))))
160+
161+-- Works
162+specDimapSyntax :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
163+specDimapSyntax = ravelDimapSyntax testDimapSyntax
164+
165+
166+-------------------------------------------------------------------------------
167+-- Fuse together rights
168+{-
169+data RightSyntax p c d where
170+ RSRight :: Fun c a -> Fun b d -> p a b -> DimapSyntax p c d
171+ RSDirty :: p a b -> DimapSyntax p a b
172+
173+liftRightSyntax :: p a b -> RightSyntax p a b
174+liftRightSyntax pab = RSDirty pab
175+
176+lowerRightSyntax :: MRight p => RightSyntax p a b -> p a b
177+lowerRightSyntax (RSDirty pab) = pab
178+lowerRightSyntax (RSRight paa) = mright pa
179+
180+instance MRight p => MRight (DimapSyntax p) where
181+ mright (DMSDirty pab) = DMSDirty (mright pab)
182+ mright (DMSMap fca fbd pab) = DMSMap (ffmap fca) (ffmap fbd) (mright pab)
183+
184+-- Don't fuse here, we do that in DimapK1 at the end
185+instance MProfunctor p => MProfunctor (DimapSyntax p) where
186+ mdimap f g (DMSDirty p) = DMSMap f g p
187+ mdimap f g (DMSMap f' g' p) = DMSMap f g (mdimap f' g' p)
188+ -}
189+-- We always want to ensure that "RightFuse p b c ~ p b c" So after
190+-- applying the "RightFuse" constructor when we encounter a right, we need
191+-- to ensure the type is "p (Either z b) (Either z c) as that's the type of
192+-- applying right
193+data RightFuse p b c where
194+ RightFuse :: (forall a d z . Fun a (Either z b) -> Fun (Either z c) d -> p a d) -> RightFuse p (Either z b) (Either z c)
195+ DirtyRightFuse :: p b c -> RightFuse p b c
196+
197+initRightFuse :: (MRight p, MProfunctor p) => p a b -> RightFuse p (Either z a) (Either z b)
198+initRightFuse pa = RightFuse (\f g -> mdimap f g (mright pa))
199+
200+liftRightFuse :: p a b -> RightFuse p a b
201+liftRightFuse pa = DirtyRightFuse pa
202+
203+lowerRightFuse :: RightFuse p a b -> p a b
204+lowerRightFuse (DirtyRightFuse pa) = pa
205+lowerRightFuse (RightFuse pa) = pa (liftFun id) (liftFun id)
206+
207+instance (MProfunctor p, MRight p) => MRight (RightFuse p) where
208+ -- Both these work but this version maintains the EitherFun invariant
209+ mright (RightFuse r) = RightFuse (\f1 f2 -> r (assocNew <.> f1) (f2 <.> assoc'New))
210+-- mright (RightFuse r) = RightFuse (\f1 f2 -> r (assoc <.> f1) (f2 <.> assoc'))
211+ mright (DirtyRightFuse pab) = initRightFuse pab
212+ {-# INLINE mright #-}
213+
214+-- Once we encounter a dimap we're done. As expressions are already in
215+-- a normal form. | We are here
216+-- |
217+-- dimap . dimap . dimap . <-| right . right . right
218+instance MProfunctor p => MProfunctor (RightFuse p) where
219+ mdimap f g (RightFuse p) = DirtyRightFuse (p f g)
220+-- RightFuse (\aezb ezcd -> p (EitherFun Left (Right . lowerFun f) <.> aezb)
221+-- (ezcd <.> EitherFun Left (Right . lowerFun g)))
222+
223+ -- We apply this adapther after floating all dimaps to the left so that
224+ -- this should never happen
225+ mdimap f g (DirtyRightFuse p) = DirtyRightFuse (mdimap f g p)
226+ {-# INLINE mdimap #-}
227+
228+-- These are assoc and assoc' but with the free theorem applied
229+assocNew = WrappedFun (plus assocg id) <.> assoc
230+assoc'New = EitherFun id (\c -> Right (Right c))
231+
232+assocg = (\eab -> case eab of
233+ Left a -> Left a
234+ Right b -> Right (Left b))
235+
236+assoc :: Fun (Either a (Either b c)) (Either (Either a b) c)
237+assoc = EitherFun (\a -> Left (Left a))
238+ (\ebc ->
239+ case ebc of
240+ Left b -> Left (Right b)
241+ Right c -> Right c)
242+
243+assoc' :: Fun (Either (Either a b) c) (Either a (Either b c))
244+assoc' = EitherFun
245+ (\eab -> case eab of
246+ Left a -> Left a
247+ Right b -> Right (Left b))
248+ (\c -> Right (Right c) )
249+
250+-- Want to only have one mright
251+testRightFuse :: (MProfunctor p, MRight p) => p Int Int -> p (Either z2 (Either z (Either z1 Int))) (Either z2 ((Either z (Either z1 Int))))
252+testRightFuse pab = mright (mright (mright pab))
253+
254+ravelRightFuse :: (MProfunctor p, MRight p) =>
255+ (RightFuse p a b -> RightFuse p s t)
256+ -> p a b -> p s t
257+ravelRightFuse l pab = lowerRightFuse (l (liftRightFuse pab))
258+
259+-- Works
260+specRightFuse :: (MProfunctor p, MRight p) => p Int Int -> p (Either z2 (Either z (Either z1 Int))) (Either z2 ((Either z (Either z1 Int))))
261+specRightFuse = ravelRightFuse testRightFuse
262+
263+------------------------------------------------------------------------------
264+-- Then only start after a right
265+-- Don't think this is needed actually
266+{-
267+
268+data RightFuse1 p b c where
269+ RightFuse1 :: p (Either z b) (Either z c) -> (RightFuse p (Either z b) (Either z c)) -> RightFuse1 p (Either z b) (Either z c)
270+ DirtyRightFuse1 :: (p b c) -> RightFuse1 p b c
271+
272+initRightFuse1 :: (MRight p, MProfunctor p) => p a b -> RightFuse1 p (Either z a) (Either z b)
273+initRightFuse1 pab = RightFuse1 (mright pab) (initRightFuse pab)
274+
275+liftRightFuse1 = DirtyRightFuse1
276+
277+lowerRightFuse1 :: RightFuse1 p a b -> p a b
278+lowerRightFuse1 (RightFuse1 pab _) = pab
279+lowerRightFuse1 (DirtyRightFuse1 pbc) = pbc
280+
281+rfdimap :: Fun c a -> Fun b d -> RightFuse p a b -> p c d
282+rfdimap f g (RightFuse rf) = rf f g
283+
284+-- Once we see a dimap, it's over, remember the normal form invariant
285+instance MProfunctor p => MProfunctor (RightFuse1 p) where
286+ mdimap f g (RightFuse1 _ d) = DirtyRightFuse1 (rfdimap f g d) -- RightFuse1 (rfdimap f g d) (mdimap f g d)
287+ mdimap f g (DirtyRightFuse1 pbc) = DirtyRightFuse1 (mdimap f g pbc)
288+
289+instance (MProfunctor p, MRight p) => MRight (RightFuse1 p) where
290+ mright (DirtyRightFuse1 pbc) = initRightFuse1 pbc
291+ mright (RightFuse1 _ d) = RightFuse1 (mright (lowerRightFuse d)) (mright d)
292+
293+-- Want to only have one mright
294+testRightFuse1 :: (MProfunctor p, MRight p) => p Int Int -> p (Either z (Either z1 Int)) ((Either z (Either z1 Int)))
295+testRightFuse1 pab = mright (mright pab)
296+
297+ravelRightFuse1 :: (MProfunctor p, MRight p) =>
298+ (RightFuse1 p a b -> RightFuse1 p s t)
299+ -> p a b -> p s t
300+ravelRightFuse1 l pab = lowerRightFuse1 (l (liftRightFuse1 pab))
301+
302+-- Fails
303+specRightFuse1 :: (MProfunctor p, MRight p) => p Int Int -> p (Either z (Either z1 Int)) ((Either z (Either z1 Int)))
304+specRightFuse1 = ravelRightFuse1 testRightFuse1
305+-}
306+------------------------------------------------------------------------------
307+-- Now apply a specialised version of the free theorem, do this before
308+-- fusing anything or we risk losing information
309+-- dimap f (either g h) (right p) = dimap (either g f) (either id h) (right p)
310+
311+data RightFreeTheorem p a b where
312+ RFRight :: p a b -> RightFreeTheorem p (Either z a) (Either z b)
313+-- RFDMap :: Fun c a -> Fun b d -> p a b -> RightFreeTheorem p c d
314+ RFDirty :: p a b -> RightFreeTheorem p a b
315+
316+liftRightFreeTheorem :: p a b -> RightFreeTheorem p a b
317+liftRightFreeTheorem = RFDirty
318+
319+lowerRightFreeTheorem :: (MProfunctor p, MRight p) => RightFreeTheorem p a b -> p a b
320+lowerRightFreeTheorem (RFDirty pab) = pab
321+--lowerRightFreeTheorem (RFDMap fa fb pab) = mdimap fa fb pab
322+lowerRightFreeTheorem (RFRight pab) = mright pab
323+
324+instance (MRight p, MProfunctor p) => MProfunctor (RightFreeTheorem p) where
325+ -- We are interested in dimap ... (right so dimap after a dirty is
326+ -- pointless so just delegate
327+ mdimap f g (RFDirty pab) = RFDirty (mdimap f g pab)
328+ -- Actually I we need to do fusion in the same pass otherwise we just do
329+ -- this for dimaps immediately next to a right BUT we do a partial fusion
330+ -- pass first which spots these opporunities
331+ mdimap f (EitherFun g h) (RFRight pab) = RFDirty (mdimap (liftFun (plus g id) <.> f) (EitherFun id h) (mright pab))
332+ mdimap f g (RFRight pab) = RFDirty (mdimap f g (mright pab))
333+ {-# INLINE mdimap #-}
334+
335+instance MRight p => MRight (RightFreeTheorem p) where
336+ mright (RFDirty pab) = RFRight pab
337+ mright (RFRight pab) = RFRight (mright pab)
338+ {-# INLINE mright #-}
339+
340+
341+plus :: (a -> b) -> (c -> d) -> Either a c -> Either b d
342+plus f g (Left x) = Left (f x)
343+plus f g (Right x) = Right (g x)
344+
345+
346+-- Want to only have one mright
347+testRightFreeTheorem :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
348+testRightFreeTheorem pab = mdimap f g (mright (mdimap f g (mright pab)))
349+ where
350+ f :: Fun Int (Either Int Int)
351+ f = liftFun Left
352+
353+ g :: Fun (Either Int Int) Int
354+ g = EitherFun (const 0) (const 0)
355+
356+ravelRightFreeTheorem :: (MProfunctor p, MRight p) =>
357+ (RightFreeTheorem p a b -> RightFreeTheorem p s t)
358+ -> p a b -> p s t
359+ravelRightFreeTheorem l pab = lowerRightFreeTheorem (l (liftRightFreeTheorem pab))
360+
361+-- Works
362+specRightFreeTheorem :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
363+specRightFreeTheorem = ravelRightFreeTheorem testRightFreeTheorem
364+
365+------------------------------------------------------------------------------
366+-- A partial dimap fusion pass which only fuses functions such that
367+-- we still have an EitherFun in the 2nd argument of dimap.
368+
369+data PartialFusion p a b where
370+ PFDMap :: Fun c a -> Fun b d -> p a b -> PartialFusion p c d
371+ PFDirty :: p a b -> PartialFusion p a b
372+
373+liftPartialFusion :: p a b -> PartialFusion p a b
374+liftPartialFusion = PFDirty
375+
376+lowerPartialFusion :: MProfunctor p => PartialFusion p a b -> p a b
377+lowerPartialFusion (PFDirty pab) = pab
378+lowerPartialFusion (PFDMap fca fbd pcd) = mdimap fca fbd pcd
379+
380+instance (MRight p, MProfunctor p) => MProfunctor (PartialFusion p) where
381+ mdimap f g (PFDirty pab) = PFDMap f g pab
382+ mdimap f g (PFDMap f' (EitherFun l r) pab) = PFDMap (f' <.> f) (EitherFun ((lowerFun g) . l) ((lowerFun g) . r)) pab
383+ mdimap f g (PFDMap f' g' pab) = PFDMap f g (mdimap f' g' pab)
384+
385+instance (MProfunctor p, MRight p) => MRight (PartialFusion p) where
386+ mright pab = PFDirty (mright (lowerPartialFusion pab))
387+
388+
389+-- We want g and g' to fuse but not the innermost dimap so we are left with
390+-- two dimaps
391+testPartialFusion :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
392+testPartialFusion pab = (mdimap id' g (mdimap (WrappedFun Left) g' (mdimap id' id' (mright pab))))
393+ where
394+ id' = WrappedFun id
395+
396+ g' :: Fun (Either Int Int) Int
397+ g' = EitherFun (const 0) (const 0)
398+
399+ g :: Fun Int Int
400+ g = WrappedFun (const 2)
401+
402+
403+ravelPartialFusion :: (MProfunctor p, MRight p) =>
404+ (PartialFusion p a b -> PartialFusion p s t)
405+ -> p a b -> p s t
406+ravelPartialFusion l pab = lowerPartialFusion (l (liftPartialFusion pab))
407+
408+-- Works
409+specPartialFusion :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
410+specPartialFusion = ravelPartialFusion testPartialFusion
411+
412+------------------------------------------------------------------------------
413+-- We also lift the function space so that we can rewrite
414+-- dimap f (either g h) (right r) = dimap (either g f) (either id h) (right r)
415+--
416+data Fun a b where
417+ WrappedFun :: (a -> b) -> Fun a b
418+-- PlusFun :: (x1 -> x2) -> (y1 -> y2) -> Fun (Either x1 y1) (Either x2 y2)
419+ EitherFun :: (x1 -> z) -> (x2 -> z) -> Fun (Either x1 x2) z
420+
421+lowerFun :: Fun a b -> a -> b
422+lowerFun (WrappedFun f) = f
423+lowerFun (EitherFun l r) = either l r
424+
425+liftFun :: (a -> b) -> Fun a b
426+liftFun = WrappedFun
427+
428+ffmap :: Functor f => Fun a b -> Fun (f a) (f b)
429+ffmap fab = liftFun (fmap (lowerFun fab))
430+
431+ddimap :: MProfunctor p => (c -> a) -> (b -> d) -> p a b -> p c d
432+ddimap f g = mdimap (liftFun f) (liftFun g)
433+
434+femap :: Fun a b -> Fun (Either z a) (Either z b)
435+femap f = EitherFun (Left . id) (Right . lowerFun f)
436+
437+-- Note that (<.>) never destroys an inner EitherFun, that is good
438+-- for the free theorem as it means we are free to fuse dimaps whenever
439+-- we like without fear of destroying this chance. For if we had the chance
440+-- before, we still will afterwards.
441+(<.>) :: Fun b c -> Fun a b -> Fun a c
442+(<.>) cb (EitherFun l r) = EitherFun (lowerFun cb . l) (lowerFun cb . r)
443+(<.>) f g = liftFun (lowerFun f . lowerFun g)
444+
445+(<.) :: Fun b c -> (a -> b) -> Fun a c
446+(<.) f g = f <.> liftFun g
447+
448+(.>) :: (b -> c) -> Fun a b -> Fun a c
449+(.>) f g = liftFun f <.> g
450+
451+-------------------------------------------------------------------------------
452+
453+class MProfunctor p where
454+ mdimap :: Fun c a -> Fun b d -> p a b -> p c d
455+
456+class MRight p where
457+ mright :: p x y -> p (Either z x) (Either z y)
458+
459+
460+-------------------------------------------------------------------------------
461+-- Note [Optimisating Prisms Plan]
462+-- We want to write prisms in a normal form of
463+-- dimap f (EitherFun id g) . right
464+--
465+-- To that end, the optimisation plan is
466+--
467+-- 1. Float dimaps to the left and rights to the right, fusing together
468+-- dimaps as we go.
469+-- 2. Fuse together rights (taking care not to introduce EitherFun with
470+-- non-id first argument
471+-- 3. Apply the right free theorem to replace EitherFun f g with EitherFun
472+-- id g
473+--
474+-- There are three different adapters for this purpose
475+--
476+-- 1. DimapSyntax
477+-- 2. RightFuse
478+-- 3. RightFreeTheorem
479+--
480+-------------------------------------------------------------------------------
481+-- Debugging help
482+
483+-- Run each stage individually
484+
485+stage1, stage2, stage3, stage4, stage5 :: (MProfunctor p, MRight p) => (forall p . (MProfunctor p, MRight p) => p a b) -> p a b
486+stage1 = lowerDimapSyntax
487+stage2 = lowerPartialFusion
488+stage3 = lowerRightFreeTheorem
489+stage4 = lowerRightFuse
490+stage5 = lowerDimapK1
491+
492+ravelStage1, ravelStage2, ravelStage3, ravelStage4, ravelStage5, ravelStage3a
493+ :: (MProfunctor p, MRight p)
494+ => (forall p . (MProfunctor p, MRight p) => p a b -> p s t) ->
495+ p a b -> p s t
496+
497+ravelStage1 = ravelDimapSyntax
498+ravelStage2 = ravelPartialFusion
499+ravelStage3 = ravelRightFreeTheorem
500+ravelStage3a = ravelDimapSyntaxFF
501+ravelStage4 = ravelRightFuse
502+ravelStage5 = ravelDimapK1
503+
504+type FusionStack p a b = DimapSyntax (RightFuse (RightFreeTheorem p)) a b
505+
506+liftFusionStack :: MProfunctor p => p a b -> FusionStack p a b
507+liftFusionStack = liftDimapSyntax . liftRightFuse . liftRightFreeTheorem
508+
509+lowerFusionStack :: (MProfunctor p, MRight p) => FusionStack p a b -> p a b
510+lowerFusionStack = lowerRightFreeTheorem . lowerRightFuse . lowerDimapSyntax
511+{-# INLINE lowerFusionStack #-}
512+
513+ravelFusionStack :: (MProfunctor p, MRight p) =>
514+ (FusionStack p a b -> FusionStack p s t)
515+ -> p a b -> p s t
516+ravelFusionStack l pab = lowerFusionStack (l (liftFusionStack pab))
517+{-# INLINE ravelFusionStack #-}
518+
519+-- Works
520+specFusionStack1 :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
521+specFusionStack1 pab = ravelFusionStack testPartialFusion pab
522+
523+specFusionStack2 :: (MProfunctor p, MRight p) => p Int Int -> p Int Int
524+specFusionStack2 pab = ravelFusionStack testRightFreeTheorem pab
525+
526+-------------------------------------------------------------------------------
527+-- A wrapped profunctor which we can make an instance of our classes.
528+--
529+
530+newtype WrappedProfunctor p a b = WrappedProfunctor { wrappedProfunctor :: (p a b) }
531+
532+instance Profunctor p => MProfunctor (WrappedProfunctor p) where
533+ mdimap f g (WrappedProfunctor p) = WrappedProfunctor (dimap (lowerFun f) (lowerFun g) p)
534+ {-# INLINE mdimap #-}
535+
536+instance Choice p => MRight (WrappedProfunctor p) where
537+ mright (WrappedProfunctor p) = WrappedProfunctor (right' p)
538+ {-# INLINE mright #-}
539+
540+
541+
542+{-
543+instance MoreProfunctor p => MoreProfunctor (MoreBoggle p) where
544+ mdimap f g (MDirty pab) = MDMap f g pab
545+ mdimap f g (MDMap f' g' pab) = MDMap (f' <.> f) (g <.> g') pab
546+ mdimap f (EitherFun l r) (MDRight pab)
547+ = MDMap (liftFun (plus l id) <.> f) (EitherFun id r) (mright pab)
548+ mdimap f g (MDRight pab) = MDMap f g (mright pab)
549+
550+ mright (MDirty pab) = MDRight pab
551+ mright (MDMap f g pab) = MDMap (ffmap f) (ffmap g) (mright pab)
552+ mright (MDRight pab) = MDMap assoc assoc' (mright pab)
553+
554+
555+
556+data WInt = Wrap { unWrap :: !Int }
557+
558+--test :: MoreProfunctor p => p Int Int -> p (Either WInt Int) (Either WInt Int)
559+--test pii = mdimap (PlusFun unWrap id) (liftFun id) (mplusp Wrap (mright pii))
560+
561+test :: MoreProfunctor p => p Int Int -> p WInt WInt
562+test pii = mdimap (liftFun too) (EitherFun Wrap Wrap) (mright pii)
563+ where
564+ too :: WInt -> Either Int Int
565+ too (Wrap 0) = Left 0
566+ too (Wrap n) = Right n
567+
568+spec1 :: forall p . Choice p => p Int Int -> p WInt WInt
569+spec1 eta = wrappedProfunctor (test (WrappedProfunctor eta))
570+
571+spec :: Choice p => p Int Int -> p WInt WInt
572+spec eta = prismRavel test eta
573+
574+injPrism :: MoreProfunctor p => MoreBoggle p s t -> p s t
575+injPrism (MDRight pa) = mright pa
576+injPrism (MDirty pa) = pa
577+injPrism (MDMap f g pa) = mdimap f g pa
578+projPrism = MDirty
579+
580+
581+prismRavel :: (Choice p)
582+ => (MoreBoggle (WrappedProfunctor p) a b -> MoreBoggle (WrappedProfunctor p) s t)
583+ -> (p a b -> p s t)
584+prismRavel coy pafb = wrappedProfunctor (injPrism (coy (projPrism (WrappedProfunctor pafb))))
585+{-# INLINE prismRavel #-}
586+-}
587+
588+
589+
test/Spec.hs+8−6
@@ -41,10 +41,13 @@ type Prism' s a = Prism s s a a
4141type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
4242type Prism s t a b =
4343 forall p f . (Choice p, Applicative f) => p a (f b) -> p s (f t)
44+
45+type PrismP s t a b =
46+ forall p . (Choice p) => p a b -> p s t
4447type Traversal' s a = forall f. Applicative f => (a -> f a) -> s -> f s
4548
46−prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
47−prism bt seta = dimap seta (either pure (fmap bt)) . right'
49+prism :: (b -> t) -> (s -> Either t a) -> PrismP s t a b
50+prism bt seta = dimap seta (either id bt) . right'
4851{-# INLINE prism #-}
4952
5053data Record4 a = MkRecord4
@@ -93,7 +96,7 @@ subtypeLensManual f record
9396
9497data Sum1 = A Char | B Int | C () | D () deriving (Generic, Show)
9598
96−sum1PrismManual :: Prism' Sum1 Int
99+sum1PrismManual :: PrismP Sum1 Sum1 Int Int
97100sum1PrismManual = prism g f
98101 where
99102 f s1 = case s1 of
@@ -130,7 +133,7 @@ typeChangingGenericPos = position @1
130133typeChangingGenericCompose :: Lens (Record3 (Record3 a)) (Record3 (Record3 b)) a b
131134typeChangingGenericCompose = field @"fieldA" . field @"fieldA"
132135
133−sum1PrismB :: Prism' Sum1 Int
136+sum1PrismB :: PrismP Sum1 Sum1 Int Int
134137sum1PrismB = _Ctor @"B"
135138
136139inspect $ 'fieldALensManual === 'fieldALensName
@@ -141,5 +144,4 @@ inspect $ 'typeChangingManual === 'typeChangingGeneric
141144inspect $ 'typeChangingManual === 'typeChangingGenericPos
142145inspect $ 'typeChangingManualCompose === 'typeChangingGenericCompose
143146inspect $ 'intTraversalManual === 'intTraversalDerived
144−
145−--inspect $ 'sum1PrismManual === 'sum1PrismB
147+inspect $ 'sum1PrismManual === 'sum1PrismB