-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathClass.hs
166 lines (144 loc) · 5.53 KB
/
Class.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
{-# 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 ( 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 always fully replaces 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 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