Skip to content

Commit b506cfd

Browse files
committed
AccParser newtype with accumulating Applicative instance
1 parent b3a429f commit b506cfd

File tree

6 files changed

+65
-43
lines changed

6 files changed

+65
-43
lines changed

Data/Aeson/AccParser.hs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{-# LANGUAGE ScopedTypeVariables #-}
2+
{-# LANGUAGE InstanceSigs #-}
3+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
module Data.Aeson.AccParser
5+
(
6+
AccParser (AccParser, getParser)
7+
, accSequence
8+
, accTraverse
9+
, (<*>+)
10+
) where
11+
12+
import Data.Aeson.Types.Internal (Parser (..), runParser)
13+
import Data.List.NonEmpty (NonEmpty ((:|)))
14+
import qualified Data.List.NonEmpty as NonEmpty
15+
16+
newtype AccParser a = AccParser { getParser :: Parser a }
17+
deriving Functor
18+
19+
instance Applicative AccParser where
20+
pure = AccParser . pure
21+
f <*> a = AccParser (getParser f <*>+ getParser a)
22+
23+
-- | A variant of 'Control.Applicative.liftA2' that lazily accumulates errors
24+
-- from both subparsers.
25+
liftP2 :: (a -> b -> c) -> Parser a -> Parser b -> Parser c
26+
liftP2 f pa pb = Parser $ \path kf ks ->
27+
runParser pa path
28+
(\(e :| es) -> kf (e :| es ++ runParser pb path NonEmpty.toList (const [])))
29+
(\a -> runParser pb path kf (\b -> ks (f a b)))
30+
{-# INLINE liftP2 #-}
31+
32+
accSequence :: Traversable t => t (Parser a) -> Parser (t a)
33+
accSequence = accTraverse id
34+
35+
accTraverse :: Traversable t => (a -> Parser b) -> t a -> Parser (t b)
36+
accTraverse f s = getParser $ traverse' (AccParser . f) s
37+
38+
-- Making sure we are using Applicative AccParser
39+
traverse' :: Traversable t => (a -> AccParser b) -> t a -> AccParser (t b)
40+
traverse' = traverse
41+
42+
infixl 4 <*>+
43+
44+
-- | A variant of ('<*>') that lazily accumulates errors from both subparsers.
45+
(<*>+) :: Parser (a -> b) -> Parser a -> Parser b
46+
(<*>+) = liftP2 id
47+
{-# INLINE (<*>+) #-}

Data/Aeson/Types.hs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ module Data.Aeson.Types
3535
, parseMaybe
3636
, ToJSON(..)
3737
, KeyValue(..)
38-
, liftP2
39-
, (<*>+)
4038
, modifyFailure
4139
, parserThrowError
4240
, parserCatchError

Data/Aeson/Types/FromJSON.hs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import Data.Aeson.Internal.Functions (mapKey)
8686
import Data.Aeson.Parser.Internal (eitherDecodeWith, jsonEOF)
8787
import Data.Aeson.Types.Generic
8888
import Data.Aeson.Types.Internal
89+
import Data.Aeson.AccParser
8990
import Data.Attoparsec.Number (Number(..))
9091
import Data.Bits (unsafeShiftR)
9192
import Data.Fixed (Fixed, HasResolution)
@@ -606,7 +607,7 @@ parseJSON2 = liftParseJSON2 parseJSON parseJSONList parseJSON parseJSONList
606607

607608
-- | Helper function to use with 'liftParseJSON'. See 'Data.Aeson.ToJSON.listEncoding'.
608609
listParser :: (Value -> Parser a) -> Value -> Parser [a]
609-
listParser f (Array xs) = fmap V.toList (accumulateTraverseVector f xs)
610+
listParser f (Array xs) = getParser $ V.toList <$> traverse (AccParser . f) xs
610611
listParser _ v = typeMismatch "[a]" v
611612
{-# INLINE listParser #-}
612613

@@ -1529,7 +1530,7 @@ instance (FromJSON1 f, FromJSON1 g, FromJSON a) => FromJSON (Sum f g a) where
15291530
instance FromJSON1 Seq.Seq where
15301531
liftParseJSON p _ = withArray "Seq a" $
15311532
fmap Seq.fromList .
1532-
accumulateSequenceList . zipWith (parseIndexedJSON p) [0..] . V.toList
1533+
accSequence . zipWith (parseIndexedJSON p) [0..] . V.toList
15331534
{-# INLINE liftParseJSON #-}
15341535

15351536
instance (FromJSON a) => FromJSON (Seq.Seq a) where
@@ -1607,7 +1608,7 @@ instance FromJSONKey UUID.UUID where
16071608

16081609
instance FromJSON1 Vector where
16091610
liftParseJSON p _ = withArray "Vector a" $
1610-
accumulateTraverseVector (uncurry $ parseIndexedJSON p) . V.indexed
1611+
accTraverse (uncurry $ parseIndexedJSON p) . V.indexed
16111612
{-# INLINE liftParseJSON #-}
16121613

16131614
instance (FromJSON a) => FromJSON (Vector a) where

Data/Aeson/Types/Internal.hs

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,18 @@
2525

2626
module Data.Aeson.Types.Internal
2727
(
28-
accumulateSequenceList
29-
, accumulateTraverseList
30-
, accumulateTraverseVector
3128

3229
-- * Core JSON types
33-
, Value(..)
30+
Value(..)
3431
, Array
3532
, emptyArray, isEmptyArray
3633
, Pair
3734
, Object
3835
, emptyObject
3936

4037
-- * Type conversion
41-
, Parser
38+
, Parser (Parser)
39+
, runParser
4240
, Result(..)
4341
, IResult(..)
4442
, JSONPathElement(..)
@@ -47,8 +45,6 @@ module Data.Aeson.Types.Internal
4745
, parse
4846
, parseEither
4947
, parseMaybe
50-
, liftP2
51-
, (<*>+)
5248
, modifyFailure
5349
, parserThrowError
5450
, parserCatchError
@@ -344,36 +340,6 @@ apP d e = do
344340
return (b a)
345341
{-# INLINE apP #-}
346342

347-
-- | A variant of 'Control.Applicative.liftA2' that lazily accumulates errors
348-
-- from both subparsers.
349-
liftP2 :: (a -> b -> c) -> Parser a -> Parser b -> Parser c
350-
liftP2 f pa pb = Parser $ \path kf ks ->
351-
runParser pa path
352-
(\(e :| es) -> kf (e :| es ++ runParser pb path NonEmpty.toList (const [])))
353-
(\a -> runParser pb path kf (\b -> ks (f a b)))
354-
{-# INLINE liftP2 #-}
355-
356-
accumulateSequenceList :: [Parser a] -> Parser [a]
357-
accumulateSequenceList = accumulateTraverseList id
358-
359-
accumulateTraverseList :: (a -> Parser b) -> [a] -> Parser [b]
360-
accumulateTraverseList f s = case s of
361-
[] -> pure mempty
362-
h : t -> (:) <$> (f h) <*>+ (accumulateTraverseList f t)
363-
364-
accumulateTraverseVector :: (a -> Parser b) -> Vector a -> Parser (Vector b)
365-
accumulateTraverseVector f v =
366-
if V.null v
367-
then pure mempty
368-
else V.cons <$> (f $ V.head v) <*>+ (accumulateTraverseVector f $ V.tail v)
369-
370-
infixl 4 <*>+
371-
372-
-- | A variant of ('<*>') that lazily accumulates errors from both subparsers.
373-
(<*>+) :: Parser (a -> b) -> Parser a -> Parser b
374-
(<*>+) = liftP2 id
375-
{-# INLINE (<*>+) #-}
376-
377343
-- | A JSON \"object\" (key\/value map).
378344
type Object = HashMap Text Value
379345

aeson.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ library
8484

8585
exposed-modules:
8686
Data.Aeson
87+
Data.Aeson.AccParser
8788
Data.Aeson.Encoding
8889
Data.Aeson.Parser
8990
Data.Aeson.Text
@@ -213,7 +214,7 @@ test-suite tests
213214
directory,
214215
dlist,
215216
filepath,
216-
generic-deriving >= 1.10 && < 1.12,
217+
generic-deriving >= 1.10 && < 1.13,
217218
ghc-prim >= 0.2,
218219
hashable >= 1.2.4.0,
219220
scientific,

stack-lts10.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
resolver: lts-10.0
2+
packages:
3+
- '.'
4+
flags:
5+
aeson:
6+
fast: true
7+
cffi: false
8+
attoparsec-iso8601:
9+
fast: true

0 commit comments

Comments
 (0)