{-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -- | The interface for types which represent changes made to other types module Data.Patch.Class where import qualified Data.Semigroupoid as Cat import Data.Functor.Identity import Data.Functor.Misc import Data.Kind (Type) import Data.Maybe #if !MIN_VERSION_base(4,11,0) import Data.Semigroup (Semigroup(..)) #endif import Data.Proxy import Data.Type.Equality ((:~:) (..)) class PatchHet p where type PatchSource p :: Type type PatchTarget p :: Type -- | Apply the patch @p a@ to the value @a@. If no change is needed, return -- 'Nothing'. applyHet :: p -> PatchSource p -> Either (PatchSource p :~: PatchTarget p) (PatchTarget p) default applyHet :: Patch p => p -> PatchSource p -> Either (PatchSource p :~: PatchTarget p) (PatchTarget p) applyHet p a = case apply p a of Nothing -> Left Refl Just a' -> Right a' -- | Apply a 'PatchHet'; if it does nothing, return the original value applyAlwaysHet :: PatchHet p => p -> PatchSource p -> PatchTarget p applyAlwaysHet p t = case applyHet p t of Left Refl -> t Right t' -> t' -- | A 'Patch' type represents a kind of change made to a datastructure. -- -- If an instance of 'Patch' is also an instance of 'Semigroup', it should obey -- the law that @applyAlways (f <> g) == applyAlways f . applyAlways g@. class ( PatchHet p , PatchSource p ~ PatchTarget p ) => Patch p where -- | Apply the patch @p a@ to the value @a@. If no change is needed, return -- 'Nothing'. apply :: p -> PatchTarget p -> Maybe (PatchTarget p) -- | Apply a 'Patch'; if it does nothing, return the original value applyAlways :: Patch p => p -> PatchTarget p -> PatchTarget p applyAlways p t = fromMaybe t $ apply p t -- | 'Identity' can be used as a 'Patch' that always fully replaces the value instance PatchHet (Identity a) where type PatchSource (Identity a) = a type PatchTarget (Identity a) = a instance Patch (Identity a) where apply (Identity a) _ = Just a -- | 'Proxy' can be used as a 'Patch' that does nothing. instance forall (a :: Type). PatchHet (Proxy a) where type PatchSource (Proxy a) = a type PatchTarget (Proxy a) = a instance forall (a :: Type). Patch (Proxy a) where apply ~Proxy _ = Nothing -- | Like '(.)', but composes functions that return patches rather than -- functions that return new values. The Semigroup instance for patches must -- apply patches right-to-left, like '(.)'. composePatchFunctions :: (Patch p, Semigroup p) => (PatchTarget p -> p) -> (PatchTarget p -> p) -> PatchTarget p -> p composePatchFunctions g f a = let fp = f a in g (applyAlways fp a) <> fp class PatchHet2Base (p :: k -> k -> *) where type PatchSource1 p :: k -> * type PatchTarget1 p :: k -> * class ( PatchHet2Base p , PatchHet (p from to) , PatchSource1 p from ~ PatchSource (p from to) , PatchTarget1 p to ~ PatchTarget (p from to) ) => PatchHet2Locally (p :: k -> k -> *) from to where instance ( PatchHet2Base p , PatchHet (p from to) , PatchSource1 p from ~ PatchSource (p from to) , PatchTarget1 p to ~ PatchTarget (p from to) ) => PatchHet2Locally (p :: k -> k -> *) from to where applyHet2Locally :: PatchHet2Locally p from to => p from to -> PatchSource1 p from -> Either (PatchSource1 p from :~: PatchTarget1 p to) (PatchTarget1 p to) applyHet2Locally = applyHet applyAlwaysHet2Locally :: PatchHet2Locally p from to => p from to -> PatchSource1 p from -> PatchTarget1 p to applyAlwaysHet2Locally = applyAlwaysHet -- TODO once we can use quantified constraints, perhaps combine PatchHet2Base and -- PatchHet2Locally, or at least get rid of this. class PatchHet2Base p => PatchHet2 (p :: k -> k -> *) where applyHet2 :: p from to -> PatchSource1 p from -> Either (PatchSource1 p from :~: PatchTarget1 p to) (PatchTarget1 p to) applyAlwaysHet2 :: PatchHet2 p => p from to -> PatchSource1 p from -> PatchTarget1 p to applyAlwaysHet2 p t = case applyHet2 p t of Left Refl -> t Right t' -> t' -- | Connect the classes without quanitified constraints newtype ProjectLocal p from to = ProjectLocal { unProjectLocal :: p from to } deriving newtype Cat.Semigroupoid instance PatchHet2 p => PatchHet (ProjectLocal p from to) where type PatchSource (ProjectLocal p from to) = PatchSource1 p from type PatchTarget (ProjectLocal p from to) = PatchTarget1 p to applyHet (ProjectLocal p) = applyHet2 p instance PatchHet2 p => PatchHet2Base (ProjectLocal p) where type PatchSource1 (ProjectLocal p) = PatchSource1 p type PatchTarget1 (ProjectLocal p) = PatchTarget1 p class ( PatchHet2Base p , PatchSource1 p ~ PatchTarget1 p ) => Patch2 p instance ( PatchHet2Base p , PatchSource1 p ~ PatchTarget1 p ) => Patch2 p -- | 'First2' can be used as a 'Patch' that always fully replaces the value instance PatchHet (First2 (t :: k -> *) (from :: k) (to :: k)) where type PatchSource (First2 t from to) = t from type PatchTarget (First2 t from to) = t to applyHet (First2 val) _ = Right val -- | 'Proxy3' can be used as a 'Patch' that always does nothing instance PatchHet (Proxy3 (t :: k -> *) (a :: k) (a :: k)) where type PatchSource (Proxy3 t a a) = t a type PatchTarget (Proxy3 t a a) = t a applyHet ~Proxy3 _ = Left Refl instance PatchHet2Base (Proxy3 (t :: k -> *) :: k -> k -> *) where type PatchSource1 (Proxy3 t) = t type PatchTarget1 (Proxy3 t) = t