Skip to content

Commit 5152882

Browse files
authored
Add Unit instances (#43)
* Add Unit instances * Void instances, docs * Add Maybe instances
1 parent e35698c commit 5152882

File tree

11 files changed

+163
-49
lines changed

11 files changed

+163
-49
lines changed

generated-docs/Data/Foreign/Class.md

+8
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ to decode your foreign/JSON-encoded data.
2525

2626
##### Instances
2727
``` purescript
28+
Decode Void
29+
Decode Unit
2830
Decode Foreign
2931
Decode String
3032
Decode Char
3133
Decode Boolean
3234
Decode Number
3335
Decode Int
3436
(Decode a) => Decode (Array a)
37+
(Decode v) => Decode (StrMap v)
38+
(Decode a) => Decode (NullOrUndefined a)
3539
```
3640

3741
#### `Encode`
@@ -59,13 +63,17 @@ to encode your data as JSON.
5963

6064
##### Instances
6165
``` purescript
66+
Encode Void
67+
Encode Unit
6268
Encode Foreign
6369
Encode String
6470
Encode Char
6571
Encode Boolean
6672
Encode Number
6773
Encode Int
6874
(Encode a) => Encode (Array a)
75+
(Encode a) => Encode (NullOrUndefined a)
76+
(Encode v) => Encode (StrMap v)
6977
```
7078

7179

generated-docs/Data/Foreign/Generic.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Default decoding/encoding options:
1111
- Represent sum types as records with `tag` and `contents` fields
1212
- Unwrap single arguments
1313
- Don't unwrap single constructors
14+
- Use the constructor names as-is
15+
- Use the field names as-is
1416

1517
#### `genericDecode`
1618

@@ -42,7 +44,7 @@ Decode a JSON string using a `Decode` instance.
4244
encodeJSON :: forall a. Encode a => a -> String
4345
```
4446

45-
Encode value that has an `Encode` instance into a JSON string.
47+
Encode a JSON string using an `Encode` instance.
4648

4749
#### `genericDecodeJSON`
4850

generated-docs/Data/Foreign/Generic/Class.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ GenericEncode NoConstructors
3232

3333
``` purescript
3434
class GenericDecodeArgs a where
35-
decodeArgs :: Int -> List Foreign -> F { result :: a, rest :: List Foreign, next :: Int }
35+
decodeArgs :: Options -> Int -> List Foreign -> F { result :: a, rest :: List Foreign, next :: Int }
3636
```
3737

3838
##### Instances
@@ -47,7 +47,7 @@ GenericDecodeArgs NoArguments
4747

4848
``` purescript
4949
class GenericEncodeArgs a where
50-
encodeArgs :: a -> List Foreign
50+
encodeArgs :: Options -> a -> List Foreign
5151
```
5252

5353
##### Instances
@@ -62,7 +62,7 @@ GenericEncodeArgs NoArguments
6262

6363
``` purescript
6464
class GenericDecodeFields a where
65-
decodeFields :: Foreign -> F a
65+
decodeFields :: Options -> Foreign -> F a
6666
```
6767

6868
##### Instances
@@ -75,7 +75,7 @@ class GenericDecodeFields a where
7575

7676
``` purescript
7777
class GenericEncodeFields a where
78-
encodeFields :: a -> StrMap Foreign
78+
encodeFields :: Options -> a -> StrMap Foreign
7979
```
8080

8181
##### Instances
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
## Module Data.Foreign.Generic.EnumEncoding
2+
3+
#### `GenericEnumOptions`
4+
5+
``` purescript
6+
type GenericEnumOptions = { constructorTagTransform :: String -> String }
7+
```
8+
9+
#### `defaultGenericEnumOptions`
10+
11+
``` purescript
12+
defaultGenericEnumOptions :: GenericEnumOptions
13+
```
14+
15+
#### `genericDecodeEnum`
16+
17+
``` purescript
18+
genericDecodeEnum :: forall a rep. Generic a rep => GenericDecodeEnum rep => GenericEnumOptions -> Foreign -> F a
19+
```
20+
21+
A generic function to be used with "Enums", or sum types with only no-argument constructors. This is used for decoding from strings to one of the constructors, combined with the `constructorTagTransform` property of `SumEncoding`.
22+
23+
#### `genericEncodeEnum`
24+
25+
``` purescript
26+
genericEncodeEnum :: forall a rep. Generic a rep => GenericEncodeEnum rep => GenericEnumOptions -> a -> Foreign
27+
```
28+
29+
A generic function to be used with "Enums", or sum types with only no-argument constructors. This is used for encoding to strings from one of the constructors, combined with the `constructorTagTransform` property of `SumEncoding`.
30+
31+
For example:
32+
33+
```purescript
34+
data Fruit = Apple | Banana | Frikandel
35+
derive instance geFruit :: Generic Fruit _
36+
instance eFruit :: Encode Fruit where
37+
encode = genericEncodeEnum defaultGenericEnumOptions
38+
39+
#### `GenericDecodeEnum`
40+
41+
``` purescript
42+
class GenericDecodeEnum a where
43+
decodeEnum :: GenericEnumOptions -> Foreign -> F a
44+
```
45+
46+
A type class for type representations that can be used for decoding to an Enum. Only the sum and no-argument constructor instances are valid, while others provide a `Fail` constraint to fail in compilation.
47+
48+
For example:
49+
50+
```purescript
51+
data Fruit = Apple | Banana | Frikandel
52+
derive instance geFruit :: Generic Fruit _
53+
instance dFruit :: Decode Fruit where
54+
decode = genericDecodeEnum defaultGenericEnumOptions
55+
```
56+
57+
##### Instances
58+
``` purescript
59+
(GenericDecodeEnum a, GenericDecodeEnum b) => GenericDecodeEnum (Sum a b)
60+
(IsSymbol name) => GenericDecodeEnum (Constructor name NoArguments)
61+
(Fail "genericEncode/DecodeEnum cannot be used on types that are not sums of constructors with no arguments.") => GenericDecodeEnum (Constructor name (Argument a))
62+
(Fail "genericEncode/DecodeEnum cannot be used on types that are not sums of constructors with no arguments.") => GenericDecodeEnum (Constructor name (Product a b))
63+
(Fail "genericEncode/DecodeEnum cannot be used on types that are not sums of constructors with no arguments.") => GenericDecodeEnum (Constructor name (Rec a))
64+
```
65+
66+
#### `GenericEncodeEnum`
67+
68+
``` purescript
69+
class GenericEncodeEnum a where
70+
encodeEnum :: GenericEnumOptions -> a -> Foreign
71+
```
72+
73+
A type class for type representations that can be used for encoding from an Enum. Only the sum and no-argument constructor instances are valid, while others provide a `Fail` constraint to fail in compilation.
74+
75+
##### Instances
76+
``` purescript
77+
(GenericEncodeEnum a, GenericEncodeEnum b) => GenericEncodeEnum (Sum a b)
78+
(IsSymbol name) => GenericEncodeEnum (Constructor name NoArguments)
79+
(Fail "genericEncode/DecodeEnum cannot be used on types that are not sums of constructors with no arguments.") => GenericEncodeEnum (Constructor name (Argument a))
80+
(Fail "genericEncode/DecodeEnum cannot be used on types that are not sums of constructors with no arguments.") => GenericEncodeEnum (Constructor name (Product a b))
81+
(Fail "genericEncode/DecodeEnum cannot be used on types that are not sums of constructors with no arguments.") => GenericEncodeEnum (Constructor name (Rec a))
82+
```
83+
84+

generated-docs/Data/Foreign/Generic/Types.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
#### `Options`
44

55
``` purescript
6-
type Options = { sumEncoding :: SumEncoding, unwrapSingleConstructors :: Boolean, unwrapSingleArguments :: Boolean }
6+
type Options = { sumEncoding :: SumEncoding, unwrapSingleConstructors :: Boolean, unwrapSingleArguments :: Boolean, fieldTransform :: String -> String }
77
```
88

99
#### `SumEncoding`
1010

1111
``` purescript
1212
data SumEncoding
13-
= TaggedObject { tagFieldName :: String, contentsFieldName :: String }
13+
= TaggedObject { tagFieldName :: String, contentsFieldName :: String, constructorTagTransform :: String -> String }
1414
```
1515

16+
The encoding of sum types for your type.
17+
`TaggedObject`s will be encoded in the form `{ [tagFieldName]: "ConstructorTag", [contentsFieldName]: "Contents"}`.
18+
`constructorTagTransform` can be provided to transform the constructor tag to a form you use, e.g. `toLower`/`toUpper`.
19+
1620

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Module Data.Foreign.Internal
2+
3+
#### `isStrMap`
4+
5+
``` purescript
6+
isStrMap :: Foreign -> Boolean
7+
```
8+
9+
Test whether a foreign value is a dictionary
10+
11+
#### `readStrMap`
12+
13+
``` purescript
14+
readStrMap :: Foreign -> F (StrMap Foreign)
15+
```
16+
17+
Attempt to coerce a foreign value to a StrMap
18+
19+

generated-docs/Data/Foreign/NullOrUndefined.md

+6
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,10 @@ readNullOrUndefined :: forall a. (Foreign -> F a) -> Foreign -> F (NullOrUndefin
3737

3838
Read a `NullOrUndefined` value
3939

40+
#### `undefined`
41+
42+
``` purescript
43+
undefined :: Foreign
44+
```
45+
4046

src/Data/Foreign/Class.purs

+23-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module Data.Foreign.Class where
22

33
import Prelude
4-
import Control.Monad.Except (mapExcept)
4+
import Control.Monad.Except (except, mapExcept)
55
import Data.Array ((..), zipWith, length)
66
import Data.Bifunctor (lmap)
7-
import Data.Foreign (F, Foreign, ForeignError(ErrorAtIndex), readArray, readBoolean, readChar, readInt, readNumber, readString, toForeign)
8-
import Data.Foreign.NullOrUndefined (NullOrUndefined(..), readNullOrUndefined, undefined)
9-
import Data.Maybe (maybe)
7+
import Data.Either (Either(..))
8+
import Data.Foreign (F, Foreign, ForeignError(..), readArray, readBoolean, readChar, readInt, readNumber, readString, toForeign)
9+
import Data.Foreign.NullOrUndefined (readNullOrUndefined, undefined)
10+
import Data.Maybe (Maybe, maybe)
1011
import Data.StrMap as StrMap
1112
import Data.Traversable (sequence)
1213
import Data.Foreign.Internal (readStrMap)
@@ -29,6 +30,12 @@ import Data.Foreign.Internal (readStrMap)
2930
class Decode a where
3031
decode :: Foreign -> F a
3132

33+
instance voidDecode :: Decode Void where
34+
decode _ = except (Left (pure (ForeignError "Decode: void")))
35+
36+
instance unitDecode :: Decode Unit where
37+
decode _ = pure unit
38+
3239
instance foreignDecode :: Decode Foreign where
3340
decode = pure
3441

@@ -55,6 +62,9 @@ instance arrayDecode :: Decode a => Decode (Array a) where
5562
readElement :: Int -> Foreign -> F a
5663
readElement i value = mapExcept (lmap (map (ErrorAtIndex i))) (decode value)
5764

65+
instance maybeDecode :: Decode a => Decode (Maybe a) where
66+
decode = readNullOrUndefined decode
67+
5868
instance strMapDecode :: (Decode v) => Decode (StrMap.StrMap v) where
5969
decode = sequence <<< StrMap.mapWithKey (\_ -> decode) <=< readStrMap
6070

@@ -76,6 +86,12 @@ instance strMapDecode :: (Decode v) => Decode (StrMap.StrMap v) where
7686
class Encode a where
7787
encode :: a -> Foreign
7888

89+
instance voidEncode :: Encode Void where
90+
encode = absurd
91+
92+
instance unitEncode :: Encode Unit where
93+
encode _ = toForeign {}
94+
7995
instance foreignEncode :: Encode Foreign where
8096
encode = id
8197

@@ -97,11 +113,8 @@ instance intEncode :: Encode Int where
97113
instance arrayEncode :: Encode a => Encode (Array a) where
98114
encode = toForeign <<< map encode
99115

100-
instance decodeNullOrUndefined :: Decode a => Decode (NullOrUndefined a) where
101-
decode = readNullOrUndefined decode
102-
103-
instance encodeNullOrUndefined :: Encode a => Encode (NullOrUndefined a) where
104-
encode (NullOrUndefined a) = maybe undefined encode a
116+
instance maybeEncode :: Encode a => Encode (Maybe a) where
117+
encode = maybe undefined encode
105118

106-
instance strMapEncode :: Encode v => Encode (StrMap.StrMap v) where
119+
instance strMapEncode :: Encode v => Encode (StrMap.StrMap v) where
107120
encode = toForeign <<< StrMap.mapWithKey (\_ -> encode)

src/Data/Foreign/NullOrUndefined.purs

+4-23
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,12 @@ module Data.Foreign.NullOrUndefined where
22

33
import Prelude
44

5-
import Data.Newtype (class Newtype, unwrap)
65
import Data.Maybe (Maybe(..))
76
import Data.Foreign (F, Foreign, isUndefined, isNull)
87

9-
-- | A `newtype` wrapper whose `IsForeign` instance correctly handles
10-
-- | null and undefined values.
11-
-- |
12-
-- | Conceptually, this type represents values which may be `null`
13-
-- | or `undefined`.
14-
newtype NullOrUndefined a = NullOrUndefined (Maybe a)
15-
16-
derive instance newtypeNullOrUndefined :: Newtype (NullOrUndefined a) _
17-
derive instance eqNullOrUndefined :: Eq a => Eq (NullOrUndefined a)
18-
derive instance ordNullOrUndefined :: Ord a => Ord (NullOrUndefined a)
19-
20-
instance showNullOrUndefined :: (Show a) => Show (NullOrUndefined a) where
21-
show x = "(NullOrUndefined " <> show (unwrap x) <> ")"
22-
23-
-- | Unwrap a `NullOrUndefined` value
24-
unNullOrUndefined :: forall a. NullOrUndefined a -> Maybe a
25-
unNullOrUndefined (NullOrUndefined m) = m
26-
27-
-- | Read a `NullOrUndefined` value
28-
readNullOrUndefined :: forall a. (Foreign -> F a) -> Foreign -> F (NullOrUndefined a)
29-
readNullOrUndefined _ value | isNull value || isUndefined value = pure (NullOrUndefined Nothing)
30-
readNullOrUndefined f value = NullOrUndefined <<< Just <$> f value
8+
-- | Read a value which may be null or undeifned.
9+
readNullOrUndefined :: forall a. (Foreign -> F a) -> Foreign -> F (Maybe a)
10+
readNullOrUndefined _ value | isNull value || isUndefined value = pure Nothing
11+
readNullOrUndefined f value = Just <$> f value
3112

3213
foreign import undefined :: Foreign

test/Main.purs

+4-7
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import Data.Foreign.Generic.Class (class GenericDecode, class GenericEncode, enc
1313
import Data.Foreign.Generic.EnumEncoding (class GenericDecodeEnum, class GenericEncodeEnum, GenericEnumOptions, genericDecodeEnum, genericEncodeEnum)
1414
import Data.Foreign.Generic.Types (Options, SumEncoding(..))
1515
import Data.Foreign.JSON (parseJSON)
16-
import Data.Foreign.NullOrUndefined (NullOrUndefined(..))
1716
import Data.Generic.Rep (class Generic)
1817
import Data.Maybe (Maybe(..))
1918
import Data.StrMap as StrMap
@@ -111,16 +110,14 @@ main :: forall eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit
111110
main = do
112111
testRoundTrip (RecordTest { foo: 1, bar: "test", baz: 'a' })
113112
testRoundTrip (Cons 1 (Cons 2 (Cons 3 Nil)))
114-
testRoundTrip (UndefinedTest {a: NullOrUndefined (Just "test")})
115-
testRoundTrip (UndefinedTest {a: NullOrUndefined Nothing})
116-
testRoundTrip [NullOrUndefined (Just "test")]
117-
testRoundTrip [NullOrUndefined (Nothing :: Maybe String)]
113+
testRoundTrip (UndefinedTest {a: Just "test"})
114+
testRoundTrip (UndefinedTest {a: Nothing})
115+
testRoundTrip [Just "test"]
116+
testRoundTrip [Nothing :: Maybe String]
118117
testRoundTrip (Apple)
119118
testRoundTrip (makeTree 0)
120119
testRoundTrip (makeTree 5)
121120
testRoundTrip (StrMap.fromFoldable [Tuple "one" 1, Tuple "two" 2])
122121
testUnaryConstructorLiteral
123122
let opts = defaultOptions { fieldTransform = toUpper }
124123
testGenericRoundTrip opts (RecordTest { foo: 1, bar: "test", baz: 'a' })
125-
126-

test/Types.purs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import Data.Foreign.Class (class Encode, class Decode, encode, decode)
88
import Data.Foreign.Generic (defaultOptions, genericDecode, genericEncode)
99
import Data.Foreign.Generic.EnumEncoding (defaultGenericEnumOptions, genericDecodeEnum, genericEncodeEnum)
1010
import Data.Foreign.Generic.Types (Options, SumEncoding(..))
11-
import Data.Foreign.NullOrUndefined (NullOrUndefined)
1211
import Data.Generic.Rep (class Generic)
1312
import Data.Generic.Rep.Eq (genericEq)
1413
import Data.Generic.Rep.Show (genericShow)
14+
import Data.Maybe (Maybe(..))
1515
import Data.Tuple (Tuple(..))
1616

1717
newtype TupleArray a b = TupleArray (Tuple a b)
@@ -103,7 +103,7 @@ instance encodeTree :: Encode a => Encode (Tree a) where
103103
encode x = genericEncode defaultOptions x
104104

105105
newtype UndefinedTest = UndefinedTest
106-
{ a :: NullOrUndefined String
106+
{ a :: Maybe String
107107
}
108108

109109
derive instance eqUT :: Eq UndefinedTest

0 commit comments

Comments
 (0)