-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
261 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
{-# LANGUAGE CPP #-} | ||
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-} | ||
{-# LANGUAGE DeriveDataTypeable #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
{-# LANGUAGE TypeFamilies #-} | ||
-- | This is internal module, which provides 'OrdHashMap' data type. | ||
module Data.Swagger.OrdHashMap ( | ||
OrdHashMap, | ||
-- * Basic interface | ||
empty, | ||
member, | ||
lookup, | ||
delete, | ||
-- * Combine | ||
unionWith, | ||
-- * Conversions | ||
keys, | ||
toList, | ||
fromList, | ||
fromHashMap, | ||
) where | ||
|
||
#ifndef MIN_VERSION_aeson | ||
#define MIN_VERSION_aeson(x,y,z) 0 | ||
#endif | ||
|
||
import Prelude () | ||
import Prelude.Compat hiding (lookup) | ||
|
||
import Control.Arrow (second) | ||
import Data.Aeson | ||
import qualified Data.Aeson.Types as JSON | ||
import Data.Data (Data, Typeable) | ||
import Data.Hashable (Hashable) | ||
import Data.List (sortBy) | ||
import Data.Ord (comparing) | ||
import Data.Text (Text) | ||
import qualified GHC.Exts as Exts | ||
|
||
import Control.Lens (Ixed(..), At(..), Index, IxValue, (<&>), _2) | ||
import Control.Monad.Trans.State.Strict (State, runState, state) | ||
|
||
import Data.HashMap.Strict (HashMap) | ||
import qualified Data.HashMap.Strict as HashMap | ||
|
||
------------------------------------------------------------------------------- | ||
-- Strict Pair Int a | ||
------------------------------------------------------------------------------- | ||
|
||
data P a = P !Int !a | ||
deriving (Functor, Foldable, Traversable, Typeable, Data) | ||
|
||
getPK :: P a -> Int | ||
getPK (P i _) = i | ||
{-# INLINE getPK #-} | ||
|
||
getPV :: P a -> a | ||
getPV (P _ a) = a | ||
{-# INLINE getPV #-} | ||
|
||
incPK :: Int -> P a -> P a | ||
incPK i (P j x) = P (i + j) x | ||
{-# INLINE incPK #-} | ||
|
||
instance Eq a => Eq (P a) where | ||
P _ a == P _ b = a == b | ||
|
||
instance Ord a => Ord (P a) where | ||
P _ a `compare` P _ b = a `compare` b | ||
|
||
instance Show a => Show (P a) where | ||
showsPrec d (P _ x) = showsPrec d x | ||
|
||
------------------------------------------------------------------------------- | ||
-- OrdHashMap | ||
------------------------------------------------------------------------------- | ||
|
||
-- | 'HashMap' which tries it's best to remember insertion order of elements. | ||
data OrdHashMap k v = OrdHashMap | ||
{ getIndex :: !Int | ||
, getOrdHashMap :: !(HashMap k (P v)) | ||
} | ||
deriving (Eq, Functor, Typeable, Data) | ||
|
||
instance (Show k, Show v) => Show (OrdHashMap k v) where | ||
showsPrec d m = showParen (d > 10) $ | ||
showString "fromList " . showsPrec 11 (toList m) | ||
|
||
-- TODO: define instance of Semigroup | ||
|
||
instance (Eq k, Hashable k) => Monoid (OrdHashMap k v) where | ||
mempty = empty | ||
mappend = unionWith const | ||
|
||
-- See https://github.com/bos/aeson/pull/341 | ||
instance ToJSON v => ToJSON (OrdHashMap Text v) where | ||
toJSON = object . map f . toList | ||
where | ||
f (k, v) = k .= v | ||
|
||
#if MIN_VERSION_aeson(0,10,0) | ||
toEncoding = pairs . mconcat . map f . toList | ||
where | ||
f (k, v) = k .= v | ||
#endif | ||
|
||
instance FromJSON v => FromJSON (OrdHashMap Text v) where | ||
parseJSON = fmap fromHashMap . parseJSON | ||
|
||
instance (Eq k, Hashable k) => Exts.IsList (OrdHashMap k v) where | ||
type Item (OrdHashMap k v) = (k, v) | ||
fromList = fromList | ||
toList = toList | ||
|
||
------------------------------------------------------------------------------- | ||
-- Lens | ||
------------------------------------------------------------------------------- | ||
|
||
type instance Index (OrdHashMap k v) = k | ||
type instance IxValue (OrdHashMap k v) = v | ||
|
||
instance (Eq k, Hashable k) => Ixed (OrdHashMap k v) where | ||
ix k f m = case lookup k m of | ||
Just v -> f v <&> \v' -> insert k v' m -- update? | ||
Nothing -> pure m | ||
{-# INLINE ix #-} | ||
|
||
instance (Eq k, Hashable k) => At (OrdHashMap k a) where | ||
at k f m = f mv <&> \r -> case r of | ||
Nothing -> maybe m (const (delete k m)) mv | ||
Just v' -> insert k v' m -- update? | ||
where mv = lookup k m | ||
|
||
------------------------------------------------------------------------------- | ||
-- Functions | ||
------------------------------------------------------------------------------- | ||
|
||
empty :: OrdHashMap k v | ||
empty = OrdHashMap 0 HashMap.empty | ||
{-# INLINE empty #-} | ||
|
||
member :: (Eq k, Hashable k) => k -> OrdHashMap k a -> Bool | ||
member k = HashMap.member k . getOrdHashMap | ||
{-# INLINE member #-} | ||
|
||
lookup :: (Eq k, Hashable k) => k -> OrdHashMap k v -> Maybe v | ||
lookup k = fmap getPV . HashMap.lookup k . getOrdHashMap | ||
{-# INLINE lookup #-} | ||
|
||
delete :: (Eq k, Hashable k) => k -> OrdHashMap k v -> OrdHashMap k v | ||
delete k (OrdHashMap i m) = OrdHashMap i $ HashMap.delete k m | ||
{-# INLINE delete #-} | ||
|
||
insert :: (Eq k, Hashable k) => k -> v -> OrdHashMap k v -> OrdHashMap k v | ||
insert k v om@(OrdHashMap i m) = | ||
OrdHashMap (i + 1) $ HashMap.insert k (P i v) m | ||
|
||
-- | The union of two maps. If a key occurs in both maps, | ||
-- the provided function (first argument) will be used to compute the result. | ||
-- | ||
-- Ordered traversal will go thru keys in the first map first. | ||
unionWith | ||
:: (Eq k, Hashable k) | ||
=> (v -> v -> v) | ||
-> OrdHashMap k v -> OrdHashMap k v -> OrdHashMap k v | ||
unionWith f (OrdHashMap i a) (OrdHashMap j b) = | ||
OrdHashMap (i + j) $ HashMap.unionWith f' a b' | ||
where | ||
b' = fmap (incPK i) b | ||
f' (P ii x) (P _ y) = P ii (f x y) | ||
|
||
keys :: OrdHashMap k v -> [k] | ||
keys = HashMap.keys . getOrdHashMap | ||
{-# INLINE keys #-} | ||
|
||
fromList :: forall k v. (Eq k, Hashable k) => [(k, v)] -> OrdHashMap k v | ||
fromList | ||
= mk | ||
. flip runState 0 | ||
. (traverse . _2) newP | ||
where | ||
mk :: ([(k, P v)], Int) -> OrdHashMap k v | ||
mk (m, i) = OrdHashMap i (HashMap.fromList m) | ||
|
||
toList :: OrdHashMap k v -> [(k, v)] | ||
toList | ||
= map (second getPV) | ||
. sortBy (comparing (getPK . snd)) | ||
. HashMap.toList | ||
. getOrdHashMap | ||
|
||
fromHashMap :: HashMap k v -> OrdHashMap k v | ||
fromHashMap = mk . flip runState 0 . traverse newP | ||
where | ||
mk (m, i) = OrdHashMap i m | ||
|
||
newP :: a -> State Int (P a) | ||
newP x = state $ \s -> (P s x, s + 1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
flags: {} | ||
packages: | ||
- '.' | ||
extra-deps: [] | ||
resolver: nightly-2016-01-26 | ||
extra-deps: | ||
- aeson-0.11.1.0 | ||
resolver: nightly-2016-03-04 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
module Data.Swagger.OrdHashMapSpec where | ||
|
||
import Prelude () | ||
import Prelude.Compat | ||
|
||
import Data.List (nubBy) | ||
import Data.Function (on) | ||
import Data.Monoid ((<>)) | ||
|
||
import Data.Swagger.OrdHashMap (OrdHashMap) | ||
import qualified Data.Swagger.OrdHashMap as OrdHashMap | ||
|
||
import Test.Hspec | ||
import Test.QuickCheck | ||
|
||
spec :: Spec | ||
spec = | ||
describe "OrdHashMap" $ do | ||
it "toList . fromList === id" $ property $ toListFromList | ||
it "toList distributes over mappend " $ property $ toListMappendDistribute | ||
|
||
toListFromList :: [(Int, Int)] -> Property | ||
toListFromList l = l' === OrdHashMap.toList (OrdHashMap.fromList l) | ||
where l' = reverse . nubBy (on (==) fst) . reverse $ l | ||
|
||
toListMappendDistribute :: [(Int, Int)] -> [(Int, Int)] -> Property | ||
toListMappendDistribute a b = rhs === lhs | ||
where | ||
a' = OrdHashMap.fromList a | ||
b' = foldr OrdHashMap.delete (OrdHashMap.fromList b) (OrdHashMap.keys a') | ||
rhs = OrdHashMap.toList (a' <> b') | ||
lhs = OrdHashMap.toList a' <> OrdHashMap.toList b' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters