Skip to content

Commit c438fe8

Browse files
authored
Merge pull request #4 from garyb/sums
Sum encoding again
2 parents 0b5542a + 975d112 commit c438fe8

File tree

2 files changed

+90
-68
lines changed

2 files changed

+90
-68
lines changed

src/Data/Codec/Argonaut/Common.purs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,35 @@ module Data.Codec.Argonaut.Common
55

66
import Prelude hiding (map)
77

8-
import Data.Argonaut.Core as J
98
import Data.Array as A
10-
import Data.Bifunctor as BF
11-
import Data.Codec (basicCodec)
129
import Data.Codec.Argonaut (JIndexedCodec, JPropCodec, JsonCodec, JsonDecodeError(..), array, boolean, char, decode, encode, index, indexedArray, int, jarray, jobject, json, null, number, object, printJsonDecodeError, prop, record, recordProp, string, (<~<), (~))
13-
import Data.Codec.Argonaut.Sum (Tag(..), taggedSum)
10+
import Data.Codec.Argonaut.Sum (taggedSum)
1411
import Data.Either (Either(..))
12+
import Data.Functor as F
1513
import Data.List as L
1614
import Data.Map as M
1715
import Data.Maybe (Maybe(..))
1816
import Data.Profunctor (dimap)
1917
import Data.StrMap as SM
20-
import Data.StrMap.ST as SMST
2118
import Data.Tuple (Tuple(..), fst, snd)
2219

2320
-- | A codec for `Maybe` values.
2421
maybe a. JsonCodec a JsonCodec (Maybe a)
25-
maybe codec = basicCodec dec enc
22+
maybe codec = taggedSum "Maybe" printTag parseTag dec enc
2623
where
27-
dec j = do
28-
obj ← decode jobject j
29-
tag ← decode (prop "tag" string) obj
30-
case tag of
31-
"Just"Just <$> decode (prop "value" codec) obj
32-
"Nothing" → pure Nothing
33-
_ → Left (AtKey "tag" (UnexpectedValue (J.fromString tag)))
34-
enc x = encode jobject $ SM.pureST do
35-
obj ← SMST.new
36-
case x of
37-
Nothing
38-
SMST.poke obj "tag" (J.fromString "Nothing")
39-
Just a → do
40-
_ ← SMST.poke obj "tag" (J.fromString "Just")
41-
SMST.poke obj "value" (encode codec a)
24+
printTag = case _ of
25+
false"Nothing"
26+
true"Just"
27+
parseTag = case _ of
28+
"Nothing"Just false
29+
"Just"Just true
30+
_ → Nothing
31+
dec = case _ of
32+
falseLeft Nothing
33+
trueRight (F.map Just <<< decode codec)
34+
enc = case _ of
35+
NothingTuple false Nothing
36+
Just a → Tuple true (Just (encode codec a))
4237

4338
-- | A codec for `Tuple` values.
4439
-- |
@@ -51,15 +46,21 @@ tuple codecA codecB = indexedArray "Tuple" $
5146

5247
-- | A codec for `Either` values.
5348
either a b. JsonCodec a JsonCodec b JsonCodec (Either a b)
54-
either codecA codecB = taggedSum dec enc
49+
either codecA codecB = taggedSum "Either" printTag parseTag dec enc
5550
where
56-
dec tag json = case tag of
57-
Tag "Left"BF.bimap (AtKey "value") Left (decode codecA json)
58-
Tag "Right"BF.bimap (AtKey "value") Right (decode codecB json)
59-
Tag t → Left (AtKey "tag" (UnexpectedValue (J.fromString t)))
51+
printTag = case _ of
52+
true"Left"
53+
false"Right"
54+
parseTag = case _ of
55+
"Left"Just true
56+
"Right"Just false
57+
_ → Nothing
58+
dec = case _ of
59+
trueRight (F.map Left <<< decode codecA)
60+
falseRight (F.map Right <<< decode codecB)
6061
enc = case _ of
61-
Left a → Tuple (Tag "Left") (encode codecA a)
62-
Right b → Tuple (Tag "Right") (encode codecB b)
62+
Left a → Tuple true (Just (encode codecA a))
63+
Right b → Tuple false (Just (encode codecB b))
6364

6465
-- | A codec for `List` values.
6566
-- |

src/Data/Codec/Argonaut/Sum.purs

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Data.Codec.Argonaut.Sum
2-
( Tag(..)
2+
( enumSum
33
, taggedSum
44
) where
55

@@ -8,52 +8,73 @@ import Prelude
88
import Control.Monad.Reader (ReaderT(..))
99
import Control.Monad.Writer (Writer, writer)
1010
import Data.Argonaut.Core as J
11+
import Data.Bifunctor (lmap)
1112
import Data.Codec (GCodec(..), decode, encode)
12-
import Data.Codec.Argonaut (JsonCodec, JsonDecodeError, jobject, json, prop, string)
13-
import Data.Either as E
14-
import Data.Newtype (class Newtype)
13+
import Data.Codec.Argonaut (JsonCodec, JsonDecodeError(..), jobject, json, prop, string)
14+
import Data.Either (Either(..))
15+
import Data.Maybe (Maybe(..), maybe)
1516
import Data.Profunctor.Star (Star(..))
1617
import Data.StrMap as SM
1718
import Data.StrMap.ST as SMST
1819
import Data.Tuple (Tuple(..))
1920

20-
-- | A tag value for a case in a sum type.
21-
newtype Tag = Tag String
22-
23-
derive newtype instance eqTagEq Tag
24-
derive newtype instance ordTagOrd Tag
25-
derive instance newtypeTagNewtype Tag _
21+
-- | A helper for defining JSON codecs for "enum" sum types, where every
22+
-- | constructor is nullary, and the type will be encoded as a string.
23+
enumSum
24+
a
25+
. (a String)
26+
(String Maybe a)
27+
JsonCodec a
28+
enumSum printTag parseTag = GCodec dec enc
29+
where
30+
dec ReaderT J.Json (Either JsonDecodeError) a
31+
dec = ReaderT \j → do
32+
value ← decode string j
33+
case parseTag value of
34+
Just a → Right a
35+
NothingLeft (UnexpectedValue j)
36+
enc Star (Writer J.Json) a a
37+
enc = Star \a → writer $ Tuple a (encode string (printTag a))
2638

27-
-- | A helper for defining JSON codecs for sum types.
39+
-- | A helper for defining JSON codecs for sum types. To ensure exhaustivity
40+
-- | there needs to be a mapping to and from a tag type for the type to be
41+
-- | encoded.
2842
-- |
29-
-- | - The first function attempts to decode a case, using the specified tag.
30-
-- | - The second function encodes a case, returning an appropriate tag and
31-
-- | encoded value.
43+
-- | - The first argument is the name of the type being decoded, for error
44+
-- | message purposes.
45+
-- | - The second argument maps a tag value to a string to use in the encoding.
46+
-- | - The second argument maps a string back to a tag value during decoding.
47+
-- | - The third argument returns either a constant value or a decoder function
48+
-- | based on a tag value.
49+
-- | - The fourth argument returns a tag value and optional encoded value to
50+
-- | store for a constructor of the sum.
3251
taggedSum
33-
a
34-
. (Tag J.Json E.Either JsonDecodeError a)
35-
(a Tuple Tag J.Json)
52+
tag a
53+
. String
54+
(tag String)
55+
(String Maybe tag)
56+
(tag Either a (J.Json Either JsonDecodeError a))
57+
(a Tuple tag (Maybe J.Json))
3658
JsonCodec a
37-
taggedSum f g = GCodec (decodeCase f) (encodeCase g)
38-
39-
decodeCase
40-
a
41-
. (Tag J.Json E.Either JsonDecodeError a)
42-
ReaderT J.Json (E.Either JsonDecodeError) a
43-
decodeCase f = ReaderT \j → do
44-
obj ← decode jobject j
45-
tag ← decode (prop "tag" string) obj
46-
value ← decode (prop "value" json) obj
47-
f (Tag tag) value
48-
49-
encodeCase
50-
a
51-
. (a Tuple Tag J.Json)
52-
Star (Writer J.Json) a a
53-
encodeCase f = Star case _ of
54-
a | Tuple (Tag tag) value ← f a →
55-
writer $ Tuple a $ encode jobject $
56-
SM.pureST do
57-
obj ← SMST.new
58-
_ ← SMST.poke obj "tag" (encode string tag)
59-
SMST.poke obj "value" value
59+
taggedSum name printTag parseTag f g = GCodec decodeCase encodeCase
60+
where
61+
decodeCase ReaderT J.Json (Either JsonDecodeError) a
62+
decodeCase = ReaderT \j → lmap (Named name) do
63+
obj ← decode jobject j
64+
tag ← decode (prop "tag" string) obj
65+
case parseTag tag of
66+
NothingLeft (AtKey "tag" (UnexpectedValue (J.fromString tag)))
67+
Just t →
68+
case f t of
69+
Left a → pure a
70+
Right decoder → do
71+
value ← decode (prop "value" json) obj
72+
lmap (AtKey "value") (decoder value)
73+
encodeCase Star (Writer J.Json) a a
74+
encodeCase = Star case _ of
75+
a | Tuple tag value ← g a →
76+
writer $ Tuple a $ encode jobject $
77+
SM.pureST do
78+
obj ← SMST.new
79+
_ ← SMST.poke obj "tag" (encode string (printTag tag))
80+
maybe (pure obj) (SMST.poke obj "value") value

0 commit comments

Comments
 (0)