-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Combination of #1 and #2 #10
base: develop
Are you sure you want to change the base?
Changes from 19 commits
d66d5e9
732cb14
b742380
0918153
0ec95ff
a74f716
cf7b8fb
7fd18c0
aa7311b
d72221c
f78dd16
69a7ab8
3f037af
6b8b77b
ab5bd5d
c99f2d2
98634c9
5a13577
08f13f2
6fcf213
c3cac44
584334f
6ef716a
ac8f411
48711bc
8e3015a
4da27a8
76f010e
efb8a9b
62bd0fc
055f256
76033e9
ccd725a
873bad6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{-# LANGUAGE TypeOperators #-} | ||
-- TODO upstream somwhere else? | ||
module Control.Category.DecidablyEmpty where | ||
|
||
import Control.Category | ||
import Data.Type.Equality | ||
|
||
class Category c => DecidablyEmpty c where | ||
isId :: c a b -> Maybe (a :~: b) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,53 @@ | ||
{-# LANGUAGE DefaultSignatures #-} | ||
{-# LANGUAGE DerivingStrategies #-} | ||
{-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE MultiParamTypeClasses #-} | ||
{-# LANGUAGE PolyKinds #-} | ||
{-# 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.Maybe | ||
import Data.Semigroup (Semigroup(..)) | ||
import Data.Proxy | ||
import Data.Type.Equality ((:~:) (..)) | ||
|
||
class PatchHet p where | ||
type PatchSource p :: * | ||
type PatchTarget p :: * | ||
-- | 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 Patch p where | ||
type PatchTarget p :: * | ||
class ( PatchHet p | ||
, PatchSource p ~ PatchTarget p | ||
) => Patch p where | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i like the way you tied these two together, it should make life easier in a lot of cases. |
||
-- | Apply the patch @p a@ to the value @a@. If no change is needed, return | ||
-- 'Nothing'. | ||
apply :: p -> PatchTarget p -> Maybe (PatchTarget p) | ||
|
@@ -22,19 +57,110 @@ 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 Patch (Identity a) where | ||
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 | ||
|
||
-- | 'Identity' can be used as a 'Patch' that always fully replaces the value | ||
instance Patch (Proxy a) where | ||
-- | 'Proxy' can be used as a 'Patch' that always fully replaces the value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this comment is wrong -- a 'Patch' that never alters the value? |
||
instance PatchHet (Proxy (a :: *)) where | ||
type PatchSource (Proxy a) = a | ||
type PatchTarget (Proxy a) = a | ||
instance 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 | ||
:: (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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tried using this much in practice? how is it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was using this specific instance to emulate the old API. It works fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this actually necessary to be in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope