forked from joneshf/purescript-option
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Option.purs
3190 lines (2890 loc) · 112 KB
/
Option.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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- | There are a few different data types that encapsulate ideas in programming.
-- |
-- | Records capture the idea of a collection of key/value pairs where every key and value exist.
-- | E.g. `Record (foo :: Boolean, bar :: Int)` means that both `foo` and `bar` exist and with values all of the time.
-- |
-- | Variants capture the idea of a collection of key/value pairs where exactly one of the key/value pairs exist.
-- | E.g. `Data.Variant.Variant (foo :: Boolean, bar :: Int)` means that either only `foo` exists with a value or only `bar` exists with a value, but not both at the same time.
-- |
-- | Options capture the idea of a collection of key/value pairs where any key and value may or may not exist.
-- | E.g. `Option.Option (foo :: Boolean, bar :: Int)` means that either only `foo` exists with a value, only `bar` exists with a value, both `foo` and `bar` exist with values, or neither `foo` nor `bar` exist.
-- |
-- | The distinction between these data types means that we can describe problems more accurately.
-- | Options are typically what you find in dynamic languages or in weakly-typed static languages.
-- | Their use cases range from making APIs more flexible to interfacing with serialization formats to providing better ergonomics around data types.
-- |
-- | These data types are all specific to the PureScript language.
-- | Different data types exist in other languages that combine some of these ideas.
-- | In many languages records are a combination of both PureScript-style records and PureScript-style options.
-- | E.g. `Option.Record (foo :: Boolean) (bar :: Int)` means that `foo` exists with a value all of the time, and either `bar` exists with a value or `bar` doesn't exist with a value.
-- |
-- | Other languages might signify optional fields with a question mark.
-- | E.g. In TypeScript, the previous example would be `{ foo: boolean; bar?: number }`
-- |
-- | This is different from a required field with an optional value.
-- | In PureScript, we might signify that by using: `Record (foo :: Boolean, bar :: Data.Maybe.Maybe Int)`.
-- | In TypeScript, we might signify that by using: `{ foo: boolean; bar: number | null }`
module Option
( Option
, Record
, alter
, fromRecord
, delete
, delete'
, empty
, get
, get'
, getAll
, getWithDefault
, insert
, insert'
, jsonCodec
, jsonCodecRecord
, modify
, modify'
, optional
, recordFromRecord
, recordRename
, recordSet
, recordToRecord
, rename
, required
, set
, set'
, toRecord
, class Alter
, alter''
, class AlterOption
, alterOption
, class DecodeJsonOption
, decodeJsonOption
, class Delete
, delete''
, class DeleteOption
, deleteOption
, class EncodeJsonOption
, encodeJsonOption
, class EqOption
, eqOption
, class FromRecord
, fromRecord'
, class FromRecordOption
, fromRecordOption
, class FromRecordRequired
, fromRecordRequired
, class Get
, get''
, class GetOption
, getOption
, class GetAll
, getAll'
, class GetAllOption
, getAllOption
, class Insert
, insert''
, class InsertOption
, insertOption
, class JsonCodec
, jsonCodec'
, class JsonCodecOption
, jsonCodecOption
, class JsonCodecRequired
, jsonCodecRequired
, class Modify
, modify''
, class ModifyOption
, modifyOption
, class OrdOption
, compareOption
, class Partition
, class ReadForeignOption
, readImplOption
, class Rename
, rename'
, class RenameOptional
, renameOptional
, class RenameRequired
, renameRequired
, class Set
, set''
, class SetOption
, setOption
, class SetRequired
, setRequired
, class ShowOption
, showOption
, class ToRecord
, toRecord'
, class ToRecordOption
, toRecordOption
, class WriteForeignOption
, writeForeignOption
, staticChecks
) where
import Prelude
import Prim hiding (Record)
import Control.Monad.Except as Control.Monad.Except
import Control.Monad.Reader.Trans as Control.Monad.Reader.Trans
import Control.Monad.Writer as Control.Monad.Writer
import Control.Monad.Writer.Class as Control.Monad.Writer.Class
import Data.Argonaut.Core as Data.Argonaut.Core
import Data.Argonaut.Decode.Class as Data.Argonaut.Decode.Class
import Data.Argonaut.Decode.Error as Data.Argonaut.Decode.Error
import Data.Argonaut.Encode.Class as Data.Argonaut.Encode.Class
import Data.Codec as Data.Codec
import Data.Codec.Argonaut as Data.Codec.Argonaut
import Data.Codec.Argonaut.Compat as Data.Codec.Argonaut.Compat
import Data.Either as Data.Either
import Data.Identity as Data.Identity
import Data.List as Data.List
import Data.Maybe as Data.Maybe
import Data.Profunctor.Star as Data.Profunctor.Star
import Data.Show as Data.Show
import Data.String as Data.String
import Data.Symbol as Data.Symbol
import Data.Tuple as Data.Tuple
import Foreign as Foreign
import Foreign.Index as Foreign.Index
import Foreign.Object as Foreign.Object
import Prim.Row as Prim.Row
import Prim.RowList as Prim.RowList
import Record as Record
import Record.Builder as Record.Builder
import Simple.JSON as Simple.JSON
import Type.Proxy as Type.Proxy
import Unsafe.Coerce as Unsafe.Coerce
-- | A collection of key/value pairs where any key and value may or may not exist.
-- | E.g. `Option (foo :: Boolean, bar :: Int)` means that either only `foo` exists with a value, only `bar` exists with a value, both `foo` and `bar` exist with values, or neither `foo` nor `bar` exist.
newtype Option (row :: Row Type)
= Option (Foreign.Object.Object (forall a. a))
-- | This instance ignores keys that do not exist in the given JSON object.
-- |
-- | If a key does not exist in the JSON object, it will not be added to the `Option _`.
-- |
-- | If a key does exists in the JSON object but the value cannot be successfully decoded, it will fail with an error.
-- |
-- | If a key does exists in the JSON object and the value can be successfully decoded, it will be added to the `Option _`.
instance decodeJsonOptionOption ::
( DecodeJsonOption list option
, Prim.RowList.RowToList option list
) =>
Data.Argonaut.Decode.Class.DecodeJson (Option option) where
decodeJson ::
Data.Argonaut.Core.Json ->
Data.Either.Either Data.Argonaut.Decode.Error.JsonDecodeError (Option option)
decodeJson json = case Data.Argonaut.Decode.Class.decodeJson json of
Data.Either.Left error -> Data.Either.Left error
Data.Either.Right object -> decodeJsonOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list) object
-- | This instance ignores keys that do not exist.
-- |
-- | If a key does not exist in the given `Option _`, it is not added to the JSON object.
-- |
-- | If a key does exists in the given `Option _`, it encodes it like normal and adds it to the JSON object.
instance encodeJsonOptionOption ::
( EncodeJsonOption list option
, Prim.RowList.RowToList option list
) =>
Data.Argonaut.Encode.Class.EncodeJson (Option option) where
encodeJson ::
Option option ->
Data.Argonaut.Core.Json
encodeJson option = Data.Argonaut.Core.fromObject (encodeJsonOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list) option)
instance eqOptionOption ::
( EqOption list option
, Prim.RowList.RowToList option list
) =>
Eq (Option option) where
eq = eqOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list)
instance ordOptionOption ::
( OrdOption list option
, Prim.RowList.RowToList option list
) =>
Ord (Option option) where
compare = compareOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list)
-- | This instance ignores keys that do not exist in the given `Foreign`.
-- |
-- | If a key does not exist in the `Foreign`, it will not be added to the `Option _`.
-- |
-- | If a key does exists in the `Foreign` but the value cannot be successfully read, it will fail with an error.
-- |
-- | If a key does exists in the `Foreign` and the value can be successfully read, it will be added to the `Option _`.
instance readForeignOptionOption ::
( Prim.RowList.RowToList option list
, ReadForeignOption list option
) =>
Simple.JSON.ReadForeign (Option option) where
readImpl ::
Foreign.Foreign ->
Foreign.F (Option option)
readImpl = readImplOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list)
instance showOptionOption ::
( Prim.RowList.RowToList option list
, ShowOption list option
) =>
Show (Option option) where
show ::
Option option ->
String
show option = "(Option.fromRecord {" <> go fields <> "})"
where
fields :: Data.List.List String
fields = showOption proxy option
go :: Data.List.List String -> String
go x' = case x' of
Data.List.Cons x Data.List.Nil -> " " <> x <> " "
Data.List.Cons x y -> " " <> go' x y <> " "
Data.List.Nil -> ""
go' :: String -> Data.List.List String -> String
go' acc x' = case x' of
Data.List.Cons x y -> go' (acc <> ", " <> x) y
Data.List.Nil -> acc
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
-- | This instance ignores keys that do not exist.
-- |
-- | If a key does not exist in the given `Option _`, it is not added to the `Foreign`.
-- |
-- | If a key does exists in the given `Option _`, it writes it like normal and adds it to the `Foreign`.
instance writeForeignOptionOption ::
( Prim.RowList.RowToList option list
, WriteForeignOption list option
) =>
Simple.JSON.WriteForeign (Option option) where
writeImpl ::
Option option ->
Foreign.Foreign
writeImpl = writeForeignOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list)
-- | A combination of both language-level records and options.
-- | E.g. `Option.Record (foo :: Boolean) (bar :: Int)` means that `foo` exists with a value all of the time, and either `bar` exists with a value or `bar` doesn't exist with a value.
newtype Record (required :: Row Type) (optional :: Row Type)
= Record
{ required :: Prim.Record required
, optional :: Option optional
}
derive newtype instance eqRecordRequiredOptional ::
( Eq (Option optional)
, Eq (Prim.Record required)
) =>
Eq (Record required optional)
derive newtype instance ordRecordRequiredOptional ::
( Ord (Option optional)
, Ord (Prim.Record required)
) =>
Ord (Record required optional)
-- | For required fields:
-- |
-- | If a key does not exist in the JSON object, it will fail with an error.
-- |
-- | If a key does exists in the JSON object but the value cannot be successfully decoded, it will fail with an error.
-- |
-- | If a key does exists in the JSON object and the value can be successfully decoded, it will be added to the `Option.Record _ _`.
-- |
-- | For optional fields:
-- |
-- | This instance ignores keys that do not exist in the given JSON object.
-- |
-- | If a key does not exist in the JSON object, it will not be added to the `Option.Record _ _`.
-- |
-- | If a key does exists in the JSON object but the value cannot be successfully decoded, it will fail with an error.
-- |
-- | If a key does exists in the JSON object and the value can be successfully decoded, it will be added to the `Option.Record _ _`.
instance decodeJsonRecordRequiredOptional ::
( Data.Argonaut.Decode.Class.DecodeJson (Option optional)
, Data.Argonaut.Decode.Class.DecodeJson (Prim.Record required)
) =>
Data.Argonaut.Decode.Class.DecodeJson (Record required optional) where
decodeJson ::
Data.Argonaut.Core.Json ->
Data.Either.Either Data.Argonaut.Decode.Error.JsonDecodeError (Record required optional)
decodeJson json = case Data.Argonaut.Decode.Class.decodeJson json of
Data.Either.Left error -> Data.Either.Left error
Data.Either.Right required' -> case Data.Argonaut.Decode.Class.decodeJson json of
Data.Either.Left error -> Data.Either.Left error
Data.Either.Right optional' ->
Data.Either.Right
( recordFromRecordAndOption
{ optional: optional'
, required: required'
}
)
-- | For required fields:
-- |
-- | Every key in the given `Option.Record _ _` is encoded like normal and added to the JSON object.
-- |
-- | For optional fields:
-- |
-- | This instance ignores keys that do not exist.
-- |
-- | If a key does not exist in the given `Option.Record _ _`, it is not added to the JSON object.
-- |
-- | If a key does exists in the given `Option.Record _ _`, it encodes it like normal and adds it to the JSON object.
instance encodeJsonRecordRequiredOptional ::
( Data.Argonaut.Encode.Class.GEncodeJson required requiredList
, EncodeJsonOption optionalList optional
, Prim.RowList.RowToList optional optionalList
, Prim.RowList.RowToList required requiredList
) =>
Data.Argonaut.Encode.Class.EncodeJson (Record required optional) where
encodeJson ::
Record required optional ->
Data.Argonaut.Core.Json
encodeJson record =
Data.Argonaut.Core.fromObject
( Foreign.Object.union
requiredJSON
optionalJSON
)
where
optionalJSON :: Foreign.Object.Object Data.Argonaut.Core.Json
optionalJSON = encodeJsonOption optionalProxy (optional record)
optionalProxy :: Type.Proxy.Proxy optionalList
optionalProxy = Type.Proxy.Proxy
requiredJSON :: Foreign.Object.Object Data.Argonaut.Core.Json
requiredJSON = Data.Argonaut.Encode.Class.gEncodeJson (required record) requiredProxy
requiredProxy :: Type.Proxy.Proxy requiredList
requiredProxy = Type.Proxy.Proxy
-- | For required fields:
-- |
-- | If a key does not exist in the `Foreign.Foreign`, it will fail with an error.
-- |
-- | If a key does exists in the `Foreign.Foreign` but the value cannot be successfully read, it will fail with an error.
-- |
-- | If a key does exists in the `Foreign.Foreign` and the value can be successfully read, it will be added to the `Option.Record _ _`.
-- |
-- | For optional fields:
-- |
-- | This instance ignores keys that do not exist in the given `Foreign.Foreign`.
-- |
-- | If a key does not exist in the `Foreign.Foreign`, it will not be added to the `Option.Record _ _`.
-- |
-- | If a key does exists in the `Foreign.Foreign` but the value cannot be successfully read, it will fail with an error.
-- |
-- | If a key does exists in the `Foreign.Foreign` and the value can be successfully read, it will be added to the `Option.Record _ _`.
instance readForeignRecordRequiredOptional ::
( Simple.JSON.ReadForeign (Option optional)
, Simple.JSON.ReadForeign (Prim.Record required)
) =>
Simple.JSON.ReadForeign (Record required optional) where
readImpl ::
Foreign.Foreign ->
Foreign.F (Record required optional)
readImpl foreign' = do
required' <- Simple.JSON.readImpl foreign'
optional' <- Simple.JSON.readImpl foreign'
pure
( recordFromRecordAndOption
{ optional: optional'
, required: required'
}
)
instance showRecord ::
( Data.Show.ShowRecordFields requiredList required
, Prim.RowList.RowToList optional optionalList
, Prim.RowList.RowToList required requiredList
, ShowOption optionalList optional
) =>
Show (Record required optional) where
show ::
Record required optional ->
String
show record' = "(Option.recordFromRecord {" <> go <> "})"
where
go :: String
go = case requiredFields of
[] -> case optionalFields of
Data.List.Cons x Data.List.Nil -> " " <> x <> " "
Data.List.Cons x y -> " " <> go' x y <> " "
Data.List.Nil -> ""
fields -> case optionalFields of
Data.List.Cons x Data.List.Nil -> " " <> Data.String.joinWith ", " fields <> ", " <> x <> " "
Data.List.Cons x y -> " " <> Data.String.joinWith ", " fields <> ", " <> go' x y <> " "
Data.List.Nil -> " " <> Data.String.joinWith ", " fields <> " "
go' ::
String ->
Data.List.List String ->
String
go' acc fields' = case fields' of
Data.List.Cons field fields -> go' (acc <> ", " <> field) fields
Data.List.Nil -> acc
optionalFields :: Data.List.List String
optionalFields = showOption optionalProxy (optional record')
optionalProxy :: Type.Proxy.Proxy optionalList
optionalProxy = Type.Proxy.Proxy
requiredFields :: Array String
requiredFields = Data.Show.showRecordFields requiredProxy (required record')
requiredProxy :: Type.Proxy.Proxy requiredList
requiredProxy = Type.Proxy.Proxy
-- | For required fields:
-- |
-- | Every key in the given `Option.Record _ _` is written like normal and added to the `Foreign.Foreign`.
-- |
-- | For optional fields:
-- |
-- | This instance ignores keys that do not exist.
-- |
-- | If a key does not exist in the given `Option.Record _ _`, it is not added to the `Foreign`.
-- |
-- | If a key does exists in the given `Option.Record _ _`, it writes it like normal and adds it to the `Foreign.Foreign`.
instance writeForeignRecordRequiredOptional ::
( Simple.JSON.WriteForeign (Option optional)
, Simple.JSON.WriteForeign (Prim.Record required)
) =>
Simple.JSON.WriteForeign (Record required optional) where
writeImpl ::
Record required optional ->
Foreign.Foreign
writeImpl record =
Foreign.unsafeToForeign
( Foreign.Object.union
requiredObject
optionalObject
)
where
optionalForeign :: Foreign.Foreign
optionalForeign = Simple.JSON.writeImpl (optional record)
optionalObject :: Foreign.Object.Object Foreign.Foreign
optionalObject = Foreign.unsafeFromForeign optionalForeign
requiredForeign :: Foreign.Foreign
requiredForeign = Simple.JSON.writeImpl (required record)
requiredObject :: Foreign.Object.Object Foreign.Foreign
requiredObject = Foreign.unsafeFromForeign requiredForeign
-- | A typeclass that manipulates the values in an `Option _`.
-- |
-- | If the field exists in the `Option _`, the given function is applied to `Just` the value.
-- |
-- | If the field does not exist in the `Option _`, the given function is applied to `Nothing`.
-- |
-- | If the function returns `Just a` the option will contain that field with the value `a`.
-- |
-- | If the function returns `Nothing`, the option will not contain that field.
-- |
-- | E.g.
-- | ```PureScript
-- | someOption :: Option.Option ( foo :: Boolean, bar :: Int )
-- | someOption = Option.insert (Type.Proxy.Proxy :: _ "bar") 31 Option.empty
-- | anotherOption :: Option.Option ( foo :: Boolean, bar :: Int )
-- | anotherOption = Option.alter'' { foo: \_ -> Data.Maybe.Just false, bar: \_ -> Data.Maybe.Just 41 } someOption
-- | ```
class Alter (record :: Row Type) (option' :: Row Type) (option :: Row Type) | record option -> option', record option' -> option where
alter'' ::
Prim.Record record ->
Option option' ->
Option option
-- | This instance manipulates the values in an `Option _`.
instance alterAny ::
( AlterOption list record option' option
, Prim.RowList.RowToList record list
) =>
Alter record option' option where
alter'' ::
Prim.Record record ->
Option option' ->
Option option
alter'' record option = alterOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list) record option
-- | A typeclass that iterates a `Prim.RowList.RowList` manipulating values in an `Option _`.
class AlterOption (list :: Prim.RowList.RowList Type) (record :: Row Type) (option' :: Row Type) (option :: Row Type) | list option -> option', list option' -> option where
alterOption ::
Type.Proxy.Proxy list ->
Prim.Record record ->
Option option' ->
Option option
instance alterOptionNil ::
AlterOption Prim.RowList.Nil record option option where
alterOption ::
Type.Proxy.Proxy Prim.RowList.Nil ->
Prim.Record record ->
Option option ->
Option option
alterOption _ _ option = option
else instance alterOptionCons ::
( AlterOption list record oldOption' option'
, Data.Symbol.IsSymbol label
, Prim.Row.Cons label (Data.Maybe.Maybe value' -> Data.Maybe.Maybe value) record' record
, Prim.Row.Cons label value option' option
, Prim.Row.Cons label value' oldOption' oldOption
, Prim.Row.Lacks label oldOption'
, Prim.Row.Lacks label option'
) =>
AlterOption (Prim.RowList.Cons label (Data.Maybe.Maybe value' -> Data.Maybe.Maybe value) list) record oldOption option where
alterOption ::
Type.Proxy.Proxy (Prim.RowList.Cons label (Data.Maybe.Maybe value' -> Data.Maybe.Maybe value) list) ->
Prim.Record record ->
Option oldOption ->
Option option
alterOption _ record oldOption = case recordValue optionValue of
Data.Maybe.Just value -> insert label value option
Data.Maybe.Nothing -> insertField label option
where
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
oldOption' :: Option oldOption'
oldOption' = delete label oldOption
optionValue :: Data.Maybe.Maybe value'
optionValue = get label oldOption
option :: Option option'
option = alterOption proxy record oldOption'
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
recordValue ::
Data.Maybe.Maybe value' ->
Data.Maybe.Maybe value
recordValue = Record.get label record
-- | A typeclass that iterates a `RowList` decoding an `Object Json` to an `Option _`.
class DecodeJsonOption (list :: Prim.RowList.RowList Type) (option :: Row Type) | list -> option where
decodeJsonOption ::
Type.Proxy.Proxy list ->
Foreign.Object.Object Data.Argonaut.Core.Json ->
Data.Either.Either Data.Argonaut.Decode.Error.JsonDecodeError (Option option)
instance decodeJsonOptionNil :: DecodeJsonOption Prim.RowList.Nil option where
decodeJsonOption ::
Type.Proxy.Proxy Prim.RowList.Nil ->
Foreign.Object.Object Data.Argonaut.Core.Json ->
Data.Either.Either Data.Argonaut.Decode.Error.JsonDecodeError (Option option)
decodeJsonOption _ _ = Data.Either.Right empty
else instance decodeJsonOptionCons ::
( Data.Argonaut.Decode.Class.DecodeJson value
, Data.Symbol.IsSymbol label
, DecodeJsonOption list option'
, Prim.Row.Cons label value option' option
, Prim.Row.Lacks label option'
) =>
DecodeJsonOption (Prim.RowList.Cons label value list) option where
decodeJsonOption ::
Type.Proxy.Proxy (Prim.RowList.Cons label value list) ->
Foreign.Object.Object Data.Argonaut.Core.Json ->
Data.Either.Either Data.Argonaut.Decode.Error.JsonDecodeError (Option option)
decodeJsonOption _ object' = case Foreign.Object.lookup key object' of
Data.Maybe.Just json -> do
value' <- Data.Argonaut.Decode.Class.decodeJson json
option <- option'
case value' of
Data.Maybe.Just value -> Data.Either.Right (insert label value option)
Data.Maybe.Nothing -> Data.Either.Right (insertField label option)
Data.Maybe.Nothing -> do
option <- option'
Data.Either.Right (insertField label option)
where
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
key :: String
key = Data.Symbol.reflectSymbol label
option' :: Data.Either.Either Data.Argonaut.Decode.Error.JsonDecodeError (Option option')
option' = decodeJsonOption proxy object'
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
-- | A typeclass that removes keys from an option
-- |
-- | ```PureScript
-- | someOption :: Option.Option ( foo :: Boolean, bar :: Int )
-- | someOption = Option.fromRecord { foo: true, bar: 31 }
-- |
-- | anotherOption :: Option.Option ( bar :: Int )
-- | anotherOption = Option.delete'' { foo: unit } someOption
-- | ```
class Delete (record :: Row Type) (option' :: Row Type) (option :: Row Type) | record option' -> option, record option -> option', option' option -> record where
delete'' ::
Prim.Record record ->
Option option' ->
Option option
-- | This instance removes keys from an `Option _`.
instance deleteAny ::
( DeleteOption list record option' option
, Prim.RowList.RowToList record list
) =>
Delete record option' option where
delete'' ::
Prim.Record record ->
Option option' ->
Option option
delete'' = deleteOption (Type.Proxy.Proxy :: Type.Proxy.Proxy list)
-- | A typeclass that iterates a `Prim.RowList.RowList` removing keys from `Option _`.
class DeleteOption (list :: Prim.RowList.RowList Type) (record :: Row Type) (option' :: Row Type) (option :: Row Type) | list option' -> option, list option -> option' where
deleteOption ::
Type.Proxy.Proxy list ->
Prim.Record record ->
Option option' ->
Option option
instance deleteOptionNil ::
DeleteOption Prim.RowList.Nil record option option where
deleteOption ::
Type.Proxy.Proxy Prim.RowList.Nil ->
Prim.Record record ->
Option option ->
Option option
deleteOption _ _ option = option
else instance deleteOptionCons ::
( Data.Symbol.IsSymbol label
, DeleteOption list record oldOption' option
, Prim.Row.Cons label value oldOption' oldOption
, Prim.Row.Lacks label oldOption'
) =>
DeleteOption (Prim.RowList.Cons label Unit list) record oldOption option where
deleteOption ::
Type.Proxy.Proxy (Prim.RowList.Cons label Unit list) ->
Prim.Record record ->
Option oldOption ->
Option option
deleteOption _ record option' = deleteOption proxy record option
where
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
option :: Option oldOption'
option = delete label option'
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
-- | A typeclass that iterates a `RowList` encoding an `Option _` as `Json`.
class EncodeJsonOption (list :: Prim.RowList.RowList Type) (option :: Row Type) | list -> option where
encodeJsonOption ::
Type.Proxy.Proxy list ->
Option option ->
Foreign.Object.Object Data.Argonaut.Core.Json
instance encodeJsonOptionNil ::
EncodeJsonOption Prim.RowList.Nil option where
encodeJsonOption ::
Type.Proxy.Proxy Prim.RowList.Nil ->
Option option ->
Foreign.Object.Object Data.Argonaut.Core.Json
encodeJsonOption _ _ = Foreign.Object.empty
else instance encodeJsonOptionCons ::
( Data.Argonaut.Encode.Class.EncodeJson value
, Data.Symbol.IsSymbol label
, EncodeJsonOption list option
, Prim.Row.Cons label value option' option
) =>
EncodeJsonOption (Prim.RowList.Cons label value list) option where
encodeJsonOption ::
Type.Proxy.Proxy (Prim.RowList.Cons label value list) ->
Option option ->
Foreign.Object.Object Data.Argonaut.Core.Json
encodeJsonOption _ option = case value' of
Data.Maybe.Just value ->
Foreign.Object.insert
key
(Data.Argonaut.Encode.Class.encodeJson value)
json
Data.Maybe.Nothing -> json
where
json :: Foreign.Object.Object Data.Argonaut.Core.Json
json = encodeJsonOption proxy option
key :: String
key = Data.Symbol.reflectSymbol label
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
value' :: Data.Maybe.Maybe value
value' = get label option
-- | A typeclass that iterates a `RowList` converting an `Option _` to a `Boolean`.
class EqOption (list :: Prim.RowList.RowList Type) (option :: Row Type) | list -> option where
eqOption ::
Type.Proxy.Proxy list ->
Option option ->
Option option ->
Boolean
instance eqOptionNil :: EqOption Prim.RowList.Nil option where
eqOption ::
Type.Proxy.Proxy Prim.RowList.Nil ->
Option option ->
Option option ->
Boolean
eqOption _ _ _ = true
else instance eqOptionCons ::
( Data.Symbol.IsSymbol label
, Eq value
, EqOption list option
, Prim.Row.Cons label value option' option
) =>
EqOption (Prim.RowList.Cons label value list) option where
eqOption ::
Type.Proxy.Proxy (Prim.RowList.Cons label value list) ->
Option option ->
Option option ->
Boolean
eqOption _ left right = leftValue == rightValue && rest
where
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
leftValue :: Data.Maybe.Maybe value
leftValue = get label left
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
rest :: Boolean
rest = eqOption proxy left right
rightValue :: Data.Maybe.Maybe value
rightValue = get label right
-- | A typeclass for converting a `Record _` into an `Option _`.
-- |
-- | An instance `FromRecord record required optional` states that we can make a `Record required` and an `Option optional` from a `Record record` where every required field is in the record and the rest of the present fields in the record is present in the option.
-- | E.g. `FromRecord () () ( name :: String )` says that the `Record ()` has no fields and the `Option ( name :: String )` will have no value;
-- | `FromRecord ( name :: String ) () ( name :: String )` says that the `Record ()` has no fields and the `Option ( name :: String )` will have the given `name` value;
-- | `FromRecord ( name :: String ) ( name :: String ) ()` says that the `Record ( name :: String )` has the given `name` value and the `Option ()` will have no value;
-- | `FromRecord () ( name :: String) ()` is a type error since the `name` field is required but the given record lacks the field.
-- |
-- | Since there is syntax for creating records, but no syntax for creating options, this typeclass can be useful for providing an easier to use interface to options.
-- |
-- | E.g. Someone can say:
-- | ```PureScript
-- | Option.fromRecord' { foo: true, bar: 31 }
-- | ```
-- | Instead of having to say:
-- | ```PureScript
-- | Option.insert
-- | (Type.Proxy.Proxy :: _ "foo")
-- | true
-- | ( Option.insert
-- | (Type.Proxy.Proxy :: _ "bar")
-- | 31
-- | Option.empty
-- | )
-- | ```
-- |
-- | Not only does it save a bunch of typing, it also mitigates the need for a direct dependency on `SProxy _`.
class FromRecord (record :: Row Type) (required :: Row Type) (optional :: Row Type) where
-- | The given `Record record` must have no more fields than expected.
-- |
-- | E.g. The following definitions are valid.
-- | ```PureScript
-- | option1 :: Option.Record () ( foo :: Boolean, bar :: Int )
-- | option1 = Option.fromRecord' { foo: true, bar: 31 }
-- |
-- | option2 :: Option.Record () ( foo :: Boolean, bar :: Int )
-- | option2 = Option.fromRecord' {}
-- |
-- | option3 :: Option.Record ( foo :: Boolean ) ( bar :: Int )
-- | option3 = Option.fromRecord' { foo: true }
-- | ```
-- |
-- | However, the following definitions are not valid as the given records have more fields than the expected `Option _`.
-- | ```PureScript
-- | -- This will not work as it has the extra field `baz`
-- | option3 :: Option.Record () ( foo :: Boolean, bar :: Int )
-- | option3 = Option.fromRecord' { foo: true, bar: 31, baz: "hi" }
-- |
-- | -- This will not work as it has the extra field `qux`
-- | option4 :: Option.Record () ( foo :: Boolean, bar :: Int )
-- | option4 = Option.fromRecord' { qux: [] }
-- | ```
-- |
-- | And, this definition is not valid as the given record lacks the required fields.
-- | ```PureScript
-- | option5 :: Option.Record ( baz :: String ) ( foo :: Boolean, bar :: Int )
-- | option5 = Option.fromRecord' { foo: true, bar: 31 }
-- | ```
fromRecord' ::
Prim.Record record ->
Record required optional
-- | This instance converts a record into an option.
-- |
-- | Every field in the record is added to the option.
-- |
-- | Any fields in the expected option that do not exist in the record are not added.
instance fromRecordAny ::
( FromRecordOption optionalList record optional
, FromRecordRequired requiredList record required
, Prim.Row.Union required optional' record
, Prim.RowList.RowToList optional' optionalList
, Prim.RowList.RowToList required requiredList
) =>
FromRecord record required optional where
fromRecord' ::
Prim.Record record ->
Record required optional
fromRecord' record =
recordFromRecordAndOption
{ optional: fromRecordOption (Type.Proxy.Proxy :: Type.Proxy.Proxy optionalList) record
, required: Record.Builder.build (fromRecordRequired (Type.Proxy.Proxy :: _ requiredList) record) {}
}
-- | A typeclass that iterates a `RowList` converting a `Record _` into an `Option _`.
class FromRecordOption (list :: Prim.RowList.RowList Type) (record :: Row Type) (option :: Row Type) | list -> option record where
fromRecordOption ::
Type.Proxy.Proxy list ->
Prim.Record record ->
Option option
instance fromRecordOptionNil :: FromRecordOption Prim.RowList.Nil record option where
fromRecordOption ::
Type.Proxy.Proxy Prim.RowList.Nil ->
Prim.Record record ->
Option option
fromRecordOption _ _ = empty
else instance fromRecordOptionConsMaybe ::
( Data.Symbol.IsSymbol label
, FromRecordOption list record option'
, Prim.Row.Cons label value option' option
, Prim.Row.Cons label (Data.Maybe.Maybe value) record' record
, Prim.Row.Lacks label option'
) =>
FromRecordOption (Prim.RowList.Cons label (Data.Maybe.Maybe value) list) record option where
fromRecordOption ::
Type.Proxy.Proxy (Prim.RowList.Cons label (Data.Maybe.Maybe value) list) ->
Prim.Record record ->
Option option
fromRecordOption _ record = case value' of
Data.Maybe.Just value -> insert label value option
Data.Maybe.Nothing -> insertField label option
where
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
option :: Option option'
option = fromRecordOption proxy record
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
value' :: Data.Maybe.Maybe value
value' = Record.get label record
else instance fromRecordOptionCons ::
( Data.Symbol.IsSymbol label
, FromRecordOption list record option'
, Prim.Row.Cons label value option' option
, Prim.Row.Cons label value record' record
, Prim.Row.Lacks label option'
) =>
FromRecordOption (Prim.RowList.Cons label value list) record option where
fromRecordOption ::
Type.Proxy.Proxy (Prim.RowList.Cons label value list) ->
Prim.Record record ->
Option option
fromRecordOption _ record = insert label value option
where
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
option :: Option option'
option = fromRecordOption proxy record
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
value :: value
value = Record.get label record
-- | A typeclass that iterates a `RowList` selecting the fields from a `Record _`.
class FromRecordRequired (list :: Prim.RowList.RowList Type) (record :: Row Type) (required :: Row Type) | list -> required record where
fromRecordRequired ::
Type.Proxy.Proxy list ->
Prim.Record record ->
Record.Builder.Builder (Prim.Record ()) (Prim.Record required)
instance fromRecordRequiredNil :: FromRecordRequired Prim.RowList.Nil record () where
fromRecordRequired ::
Type.Proxy.Proxy Prim.RowList.Nil ->
Prim.Record record ->
Record.Builder.Builder (Prim.Record ()) (Prim.Record ())
fromRecordRequired _ _ = identity
else instance fromRecordRequiredCons ::
( Data.Symbol.IsSymbol label
, FromRecordRequired list record required'
, Prim.Row.Cons label value record' record
, Prim.Row.Cons label value required' required
, Prim.Row.Lacks label required'
) =>
FromRecordRequired (Prim.RowList.Cons label value list) record required where
fromRecordRequired ::
Type.Proxy.Proxy (Prim.RowList.Cons label value list) ->
Prim.Record record ->
Record.Builder.Builder (Prim.Record ()) (Prim.Record required)
fromRecordRequired _ record = first <<< rest
where
first :: Record.Builder.Builder (Prim.Record required') (Prim.Record required)
first = Record.Builder.insert label value
label :: Type.Proxy.Proxy label
label = Type.Proxy.Proxy
proxy :: Type.Proxy.Proxy list
proxy = Type.Proxy.Proxy
rest :: Record.Builder.Builder (Prim.Record ()) (Prim.Record required')
rest = fromRecordRequired proxy record
value :: value
value = Record.get label record
-- | A typeclass that grabs the given fields of an `Option _`.
-- |
class Get (record' :: Row Type) (option :: Row Type) (record :: Row Type) | option record' -> record, option record -> record', record record' -> option where
-- | Attempts to fetch the values from the given option.
-- |
-- | The behavior of what's returned depends on what the value is for each field in the record.
-- |
-- | If the value in the record is of type `Maybe a -> b` ,
-- | that function is run on the result of finding the field in the option.
-- |
-- | If the value in the record is of type `Maybe a` and the type of the field in the option is `a`,
-- | the result is `Just _` if the value exists in the option and whatever the provided `Maybe a` was otherwise.
-- |
-- | If the value in the record is of type `a` and the type of the field in the option is `a`,
-- | the result is whatever the value is in the option if it exists and whatever the provided `a` was otherwise.
-- |
-- | These behaviors allow handling different fields differently without jumping through hoops to get the values from an option.
-- |
-- | E.g.
-- | ```PureScript
-- | someOption :: Option.Option ( foo :: Boolean, bar :: Int, qux :: String )
-- | someOption = Option.empty
-- |
-- | -- Since `someOption` is empty,
-- | -- this will have a shape like:
-- | -- { foo: false, bar: "not set", qux: Data.Maybe.Nothing }
-- | someRecord :: Record ( foo :: Boolean, bar :: String, qux :: Data.Maybe.Maybe String )
-- | someRecord =
-- | Option.get''