/
Variant.purs
240 lines (217 loc) · 7.45 KB
/
Variant.purs
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
module Data.Functor.Variant
( VariantF
, inj
, prj
, on
, onMatch
, case_
, match
, default
, expand
, contract
, class VariantFShows, variantFShows
, module Exports
) where
import Prelude
import Control.Alternative (class Alternative, empty)
import Data.List as L
import Data.Symbol (SProxy(..)) as Exports
import Data.Symbol (SProxy, class IsSymbol, reflectSymbol)
import Data.Variant.Internal (class Contractable, FProxy(..), class VariantFMatchCases) as Exports
import Data.Variant.Internal (class Contractable, class VariantFMatchCases, class VariantTags, FProxy, RLProxy(..), RProxy(..), VariantFCase, VariantCase, contractWith, lookup, unsafeGet, unsafeHas, variantTags)
import Partial.Unsafe (unsafeCrashWith)
import Type.Equality (class TypeEquals)
import Type.Proxy (Proxy(..))
import Type.Row as R
import Unsafe.Coerce (unsafeCoerce)
newtype VariantFRep f a = VariantFRep
{ type ∷ String
, value ∷ f a
, map ∷ ∀ x y. (x → y) → f x → f y
}
data VariantF (f ∷ # Type) a
instance functorVariantF ∷ Functor (VariantF r) where
map f a =
case coerceY a of
VariantFRep v → coerceV $ VariantFRep
{ type: v.type
, value: v.map f v.value
, map: v.map
}
where
coerceY ∷ ∀ f a. VariantF r a → VariantFRep f a
coerceY = unsafeCoerce
coerceV ∷ ∀ f a. VariantFRep f a → VariantF r a
coerceV = unsafeCoerce
-- | Inject into the variant at a given label.
-- | ```purescript
-- | maybeAtFoo :: forall r. VariantF (foo :: FProxy Maybe | r) Int
-- | maybeAtFoo = inj (SProxy :: SProxy "foo") (Just 42)
-- | ```
inj
∷ ∀ sym f a r1 r2
. RowCons sym (FProxy f) r1 r2
⇒ IsSymbol sym
⇒ Functor f
⇒ SProxy sym
→ f a
→ VariantF r2 a
inj p value = coerceV $ VariantFRep { type: reflectSymbol p, value, map }
where
coerceV ∷ VariantFRep f a → VariantF r2 a
coerceV = unsafeCoerce
-- | Attempt to read a variant at a given label.
-- | ```purescript
-- | case prj (SProxy :: SProxy "foo") maybeAtFoo of
-- | Just (Just i) -> i + 1
-- | _ -> 0
-- | ```
prj
∷ ∀ sym f a r1 r2 g
. RowCons sym (FProxy f) r1 r2
⇒ Alternative g
⇒ IsSymbol sym
⇒ SProxy sym
→ VariantF r2 a
→ g (f a)
prj p = on p pure (const empty)
-- | Attempt to read a variant at a given label by providing branches.
-- | The failure branch receives the provided variant, but with the label
-- | removed.
on
∷ ∀ sym f a b r1 r2
. RowCons sym (FProxy f) r1 r2
⇒ IsSymbol sym
⇒ SProxy sym
→ (f a → b)
→ (VariantF r1 a → b)
→ VariantF r2 a
→ b
on p f g r =
case coerceY r of
VariantFRep v | v.type == reflectSymbol p → f v.value
_ → g (coerceR r)
where
coerceY ∷ VariantF r2 a → VariantFRep f a
coerceY = unsafeCoerce
coerceR ∷ VariantF r2 a → VariantF r1 a
coerceR = unsafeCoerce
-- | Match a `VariantF` with a `Record` containing functions for handling cases.
-- | This is similar to `on`, except instead of providing a single label and
-- | handler, you can provide a record where each field maps to a particular
-- | `VariantF` case.
-- |
-- | ```purescript
-- | onMatch
-- | { foo: \foo -> "Foo: " <> maybe "nothing" id foo
-- | , bar: \bar -> "Bar: " <> snd bar
-- | }
-- | ```
-- |
-- | Polymorphic functions in records (such as `show` or `id`) can lead
-- | to inference issues if not all polymorphic variables are specified
-- | in usage. When in doubt, label methods with specific types, such as
-- | `show :: Int -> String`, or give the whole record an appropriate type.
onMatch
∷ ∀ rl r r1 r2 r3 a b
. R.RowToList r rl
⇒ VariantFMatchCases rl r1 a b
⇒ Union r1 r2 r3
⇒ Record r
→ (VariantF r2 a → b)
→ VariantF r3 a
→ b
onMatch r k v =
case coerceV v of
VariantFRep v' | unsafeHas v'.type r → unsafeGet v'.type r v'.value
_ → k (coerceR v)
where
coerceV ∷ ∀ f. VariantF r3 a → VariantFRep f a
coerceV = unsafeCoerce
coerceR ∷ VariantF r3 a → VariantF r2 a
coerceR = unsafeCoerce
-- | Combinator for exhaustive pattern matching.
-- | ```purescript
-- | caseFn :: VariantF (foo :: FProxy Maybe, bar :: FProxy (Tuple String), baz :: FProxy (Either String)) Int -> String
-- | caseFn = case_
-- | # on (SProxy :: SProxy "foo") (\foo -> "Foo: " <> maybe "nothing" show foo)
-- | # on (SProxy :: SProxy "bar") (\bar -> "Bar: " <> show (snd bar))
-- | # on (SProxy :: SProxy "baz") (\baz -> "Baz: " <> either id show baz)
-- | ```
case_ ∷ ∀ a b. VariantF () a → b
case_ r = unsafeCrashWith case unsafeCoerce r of
VariantFRep v → "Data.Functor.Variant: pattern match failure [" <> v.type <> "]"
-- | Combinator for exhaustive pattern matching using an `onMatch` case record.
-- | ```purescript
-- | matchFn :: VariantF (foo :: FProxy Maybe, bar :: FProxy (Tuple String), baz :: FProxy (Either String)) Int -> String
-- | matchFn = case_ # match
-- | { foo: \foo -> "Foo: " <> maybe "nothing" show foo
-- | , bar: \bar -> "Bar: " <> show (snd bar)
-- | , baz: \baz -> "Baz: " <> either id show baz
-- | }
-- | ```
match
∷ ∀ rl r r1 r2 a b
. R.RowToList r rl
⇒ VariantFMatchCases rl r1 a b
⇒ Union r1 () r2
⇒ Record r
→ VariantF r2 a
→ b
match r = case_ # onMatch r
-- | Combinator for partial matching with a default value in case of failure.
-- | ```purescript
-- | caseFn :: forall r. VariantF (foo :: FProxy Maybe, bar :: FProxy (Tuple String) | r) Int -> String
-- | caseFn = default "No match"
-- | # on (SProxy :: SProxy "foo") (\foo -> "Foo: " <> maybe "nothing" show foo)
-- | # on (SProxy :: SProxy "bar") (\bar -> "Bar: " <> show (snd bar))
-- | ```
default ∷ ∀ a b r. a → VariantF r b → a
default a _ = a
-- | Every `VariantF lt a` can be cast to some `VariantF gt a` as long as `lt` is a
-- | subset of `gt`.
expand
∷ ∀ lt mix gt a
. Union lt mix gt
⇒ VariantF lt a
→ VariantF gt a
expand = unsafeCoerce
-- | A `VariantF gt a` can be cast to some `VariantF lt a`, where `lt` is is a subset
-- | of `gt`, as long as there is proof that the `VariantF`'s runtime tag is
-- | within the subset of `lt`.
contract
∷ ∀ lt gt f a
. Alternative f
⇒ Contractable gt lt
⇒ VariantF gt a
→ f (VariantF lt a)
contract v =
contractWith
(RProxy ∷ RProxy gt)
(RProxy ∷ RProxy lt)
(case coerceV v of VariantFRep v' → v'.type)
(coerceR v)
where
coerceV ∷ ∀ g. VariantF gt a → VariantFRep g a
coerceV = unsafeCoerce
coerceR ∷ VariantF gt a → VariantF lt a
coerceR = unsafeCoerce
class VariantFShows (rl ∷ R.RowList) x where
variantFShows ∷ RLProxy rl → Proxy x → L.List (VariantCase → String)
instance showVariantFNil ∷ VariantFShows R.Nil x where
variantFShows _ _ = L.Nil
instance showVariantFCons ∷ (VariantFShows rs x, TypeEquals a (FProxy f), Show (f x), Show x) ⇒ VariantFShows (R.Cons sym a rs) x where
variantFShows _ p =
L.Cons (coerceShow show) (variantFShows (RLProxy ∷ RLProxy rs) p)
where
coerceShow ∷ (f x → String) → VariantCase → String
coerceShow = unsafeCoerce
instance showVariantF ∷ (R.RowToList r rl, VariantTags rl, VariantFShows rl a, Show a) ⇒ Show (VariantF r a) where
show v1 =
let
VariantFRep v = unsafeCoerce v1 ∷ VariantFRep VariantFCase a
tags = variantTags (RLProxy ∷ RLProxy rl)
shows = variantFShows (RLProxy ∷ RLProxy rl) (Proxy ∷ Proxy a)
body = lookup "show" v.type tags shows (unsafeCoerce v.value ∷ VariantCase)
in
"(inj @" <> show v.type <> " " <> body <> ")"