{-# LANGUAGE BangPatterns, CPP #-}
------------------------------------------------------------------------
-- |
-- Module : Data.FullList.Lazy
-- Copyright : 2010-2011 Johan Tibell
-- License : BSD-style
-- Maintainer : johan.tibell@gmail.com
-- Stability : provisional
-- Portability : portable
--
-- Non-empty lists of key/value pairs. The lists are strict in the
-- keys and lazy in the values.
module Data.FullList.Lazy
( FullList(..)
, List(..)
-- * Basic interface
, size
, singleton
, lookup
, insert
, delete
, insertWith
, adjust
-- * Combine
-- * Union
, union
-- * Transformations
, map
, traverseWithKey
-- * Folds
, foldlWithKey'
, foldrWithKey
-- * Filter
, filterWithKey
) where
import Control.Applicative
import Control.DeepSeq (NFData(rnf))
import Prelude hiding (lookup, map)
------------------------------------------------------------------------
-- * The 'FullList' type
-- The 'FullList' type has two benefits:
--
-- * it is guaranteed to be non-empty, and
--
-- * it can be unpacked into a data constructor.
-- Invariant: the same key only appears once in a 'FullList'.
-- | A non-empty list of key/value pairs.
data FullList k v = FL !k v !(List k v)
deriving Show
instance (Eq k, Eq v) => Eq (FullList k v) where
(FL k1 v1 xs) == (FL k2 v2 ys) = k1 == k2 && v1 == v2 && xs == ys
(FL k1 v1 xs) /= (FL k2 v2 ys) = k1 /= k2 || v1 /= v2 || xs /= ys
instance (NFData k, NFData v) => NFData (FullList k v)
data List k v = Nil | Cons !k v !(List k v)
deriving Show
instance (Eq k, Eq v) => Eq (List k v) where
(Cons k1 v1 xs) == (Cons k2 v2 ys) = k1 == k2 && v1 == v2 && xs == ys
Nil == Nil = True
_ == _ = False
(Cons k1 v1 xs) /= (Cons k2 v2 ys) = k1 /= k2 || v1 /= v2 || xs /= ys
Nil /= Nil = False
_ /= _ = True
instance (NFData k, NFData v) => NFData (List k v) where
rnf Nil = ()
rnf (Cons k v xs) = rnf k `seq` rnf v `seq` rnf xs
-- TODO: Check if evaluation is forced.
------------------------------------------------------------------------
-- * FullList
-- The 'List' functions are not inlined as they should be seldomly
-- called in practice (i.e. we expect few collisions.)
size :: FullList k v -> Int
size (FL _ _ xs) = 1 + sizeL xs
sizeL :: List k v -> Int
sizeL Nil = 0
sizeL (Cons _ _ xs) = 1 + sizeL xs
singleton :: k -> v -> FullList k v
singleton k v = FL k v Nil
lookup :: Eq k => k -> FullList k v -> Maybe v
lookup !k (FL k' v xs)
| k == k' = Just v
| otherwise = lookupL k xs
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE lookup #-}
#endif
lookupL :: Eq k => k -> List k v -> Maybe v
lookupL = go
where
go !_ Nil = Nothing
go k (Cons k' v xs)
| k == k' = Just v
| otherwise = go k xs
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE lookupL #-}
#endif
member :: Eq k => k -> FullList k v -> Bool
member !k (FL k' _ xs)
| k == k' = True
| otherwise = memberL k xs
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE member #-}
#endif
memberL :: Eq k => k -> List k v -> Bool
memberL = go
where
go !_ Nil = False
go k (Cons k' _ xs)
| k == k' = True
| otherwise = go k xs
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE memberL #-}
#endif
insert :: Eq k => k -> v -> FullList k v -> FullList k v
insert !k v (FL k' v' xs)
| k == k' = FL k v xs
| otherwise = FL k' v' (insertL k v xs)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE insert #-}
#endif
-- | /O(n)/ Insert at the head of the list to avoid copying the whole
-- list.
insertL :: Eq k => k -> v -> List k v -> List k v
insertL = go
where
go !k v Nil = Cons k v Nil
go k v (Cons k' v' xs)
| k == k' = Cons k v xs
| otherwise = Cons k' v' (go k v xs)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE insertL #-}
#endif
delete :: Eq k => k -> FullList k v -> Maybe (FullList k v)
delete !k (FL k' v xs)
| k == k' = case xs of
Nil -> Nothing
Cons k'' v' xs' -> Just $ FL k'' v' xs'
| otherwise = let ys = deleteL k xs
in ys `seq` Just (FL k' v ys)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE delete #-}
#endif
deleteL :: Eq k => k -> List k v -> List k v
deleteL = go
where
go !_ Nil = Nil
go k (Cons k' v xs)
| k == k' = xs
| otherwise = Cons k' v (go k xs)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE deleteL #-}
#endif
insertWith :: Eq k => (v -> v -> v) -> k -> v -> FullList k v -> FullList k v
insertWith f !k v (FL k' v' xs)
| k == k' = FL k (f v v') xs
| otherwise = FL k' v' (insertWithL f k v xs)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE insertWith #-}
#endif
insertWithL :: Eq k => (v -> v -> v) -> k -> v -> List k v -> List k v
insertWithL = go
where
go _ !k v Nil = Cons k v Nil
go f k v (Cons k' v' xs)
| k == k' = Cons k (f v v') xs
| otherwise = Cons k' v' (go f k v xs)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE insertWithL #-}
#endif
adjust :: Eq k => (v -> v) -> k -> FullList k v -> FullList k v
adjust f !k (FL k' v xs)
| k == k' = FL k' (f v) xs
| otherwise = FL k' v (adjustL f k xs)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE adjust #-}
#endif
adjustL :: Eq k => (v -> v) -> k -> List k v -> List k v
adjustL f = go
where
go !_ Nil = Nil
go k (Cons k' v xs)
| k == k' = Cons k' (f v) xs
| otherwise = Cons k' v (go k xs)
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE adjustL #-}
#endif
------------------------------------------------------------------------
-- * Combine
-- | /O(n^2)/ Left biased union.
union :: Eq k => FullList k v -> FullList k v -> FullList k v
union xs (FL k v ys)
| k `member` xs = unionL xs ys
| otherwise = case unionL xs ys of
FL k' v' zs -> FL k v $ Cons k' v' zs
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE union #-}
#endif
unionL :: Eq k => FullList k v -> List k v -> FullList k v
unionL xs@(FL k v zs) = FL k v . go
where
go Nil = zs
go (Cons k' v' ys)
| k' `member` xs = go ys
| otherwise = Cons k' v' $ go ys
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE unionL #-}
#endif
------------------------------------------------------------------------
-- * Transformations
map :: (k1 -> v1 -> (k2, v2)) -> FullList k1 v1 -> FullList k2 v2
map f (FL k v xs) = let (k', v') = f k v
in FL k' v' (mapL f xs)
{-# INLINE map #-}
mapL :: (k1 -> v1 -> (k2, v2)) -> List k1 v1 -> List k2 v2
mapL f = go
where
go Nil = Nil
go (Cons k v xs) = let (k', v') = f k v
in Cons k' v' (go xs)
{-# INLINE mapL #-}
traverseWithKey :: Applicative m => (k -> v1 -> m v2) -> FullList k v1 -> m (FullList k v2)
traverseWithKey f (FL k v xs) = FL k <$> f k v <*> traverseWithKeyL f xs
{-# INLINE traverseWithKey #-}
traverseWithKeyL :: Applicative m => (k -> v1 -> m v2) -> List k v1 -> m (List k v2)
traverseWithKeyL f = go
where
go Nil = pure Nil
go (Cons k v xs) = Cons k <$> f k v <*> go xs
{-# INLINE traverseWithKeyL #-}
------------------------------------------------------------------------
-- * Folds
foldlWithKey' :: (a -> k -> v -> a) -> a -> FullList k v -> a
foldlWithKey' f !z (FL k v xs) = foldlWithKey'L f (f z k v) xs
{-# INLINE foldlWithKey' #-}
foldlWithKey'L :: (a -> k -> v -> a) -> a -> List k v -> a
foldlWithKey'L f = go
where
go !z Nil = z
go z (Cons k v xs) = go (f z k v) xs
{-# INLINE foldlWithKey'L #-}
foldrWithKey :: (k -> v -> a -> a) -> a -> FullList k v -> a
foldrWithKey f z (FL k v xs) = f k v (foldrWithKeyL f z xs)
{-# INLINE foldrWithKey #-}
foldrWithKeyL :: (k -> v -> a -> a) -> a -> List k v -> a
foldrWithKeyL f = go
where
go z Nil = z
go z (Cons k v xs) = f k v (go z xs)
{-# INLINE foldrWithKeyL #-}
------------------------------------------------------------------------
-- * Filter
filterWithKey :: (k -> v -> Bool) -> FullList k v -> Maybe (FullList k v)
filterWithKey p (FL k v xs)
| p k v = Just (FL k v ys)
| otherwise = case ys of
Nil -> Nothing
Cons k' v' zs -> Just $ FL k' v' zs
where !ys = filterWithKeyL p xs
{-# INLINE filterWithKey #-}
filterWithKeyL :: (k -> v -> Bool) -> List k v -> List k v
filterWithKeyL p = go
where
go Nil = Nil
go (Cons k v xs)
| p k v = Cons k v (go xs)
| otherwise = go xs
{-# INLINE filterWithKeyL #-}