module Data.Cache.LRU.Internal where
import Control.Applicative (Applicative, pure, liftA2)
import Data.Traversable (Traversable(traverse), foldMapDefault)
import Data.Foldable (Foldable(foldMap), traverse_)
import Prelude hiding (last, lookup)
import Data.Map ( Map )
import qualified Data.Map as Map
#if MIN_VERSION_containers(0,5,0)
import qualified Data.Map.Strict as MapStrict
#endif
import Data.Data (Data)
import Data.Typeable (Typeable)
import Data.Functor.Contravariant (Contravariant((>$)))
data LRU key val = LRU {
first :: !(Maybe key)
, last :: !(Maybe key)
, maxSize :: !(Maybe Integer)
, content :: !(Map key (LinkedVal key val))
} deriving (Eq, Data, Typeable, Functor)
instance (Ord key) => Traversable (LRU key) where
traverse f l = fmap (fromList $ maxSize l) . go $ toList l
where
go [] = pure []
go (x:xs) = liftA2 (:) (g x) (go xs)
g (a, b) = fmap ((,) a) $ f b
instance (Ord key) => Foldable (LRU key) where
foldMap = foldMapDefault
instance (Ord key, Show key, Show val) => Show (LRU key val) where
show lru = "fromList " ++ show (toList lru)
data LinkedVal key val = Link {
value :: val
, prev :: !(Maybe key)
, next :: !(Maybe key)
} deriving (Eq, Data, Typeable, Functor, Foldable, Traversable)
newLRU :: (Ord key) => Maybe Integer
-> LRU key val
newLRU (Just s) | s <= 0 = error "non-positive size LRU"
newLRU s = LRU Nothing Nothing s Map.empty
fromList :: Ord key => Maybe Integer
-> [(key, val)] -> LRU key val
fromList s l = appendAll $ newLRU s
where appendAll = foldr ins id l
ins (k, v) = (insert k v .)
toList :: Ord key => LRU key val -> [(key, val)]
toList lru = maybe [] (listLinks . content $ lru) $ first lru
where
listLinks m key =
let Just lv = Map.lookup key m
keyval = (key, value lv)
in case next lv of
Nothing -> [keyval]
Just nk -> keyval : listLinks m nk
pairs :: (Ord key, Applicative f, Contravariant f)
=> ((key, val) -> f (key, val))
-> LRU key val -> f (LRU key val)
pairs f l = () >$ (traverse_ f $ toList l)
keys :: (Ord key, Applicative f, Contravariant f)
=> (key -> f key)
-> LRU key val -> f (LRU key val)
keys f l = () >$ (traverse_ (f . fst) $ toList l)
insert :: Ord key => key -> val -> LRU key val -> LRU key val
insert key val lru = fst (insertInforming key val lru)
insertInforming :: Ord key => key -> val -> LRU key val
-> (LRU key val, Maybe (key, val))
insertInforming key val lru = maybe emptyCase nonEmptyCase $ first lru
where
contents = content lru
full = maybe False (fromIntegral (Map.size contents) ==) $ maxSize lru
present = key `Map.member` contents
emptyCase = (LRU fl fl (maxSize lru) m', Nothing)
where
fl = Just key
lv = Link val Nothing Nothing
m' = Map.insert key lv contents
nonEmptyCase firstKey = if present then (hitSet, Nothing)
else add firstKey
hitSet = hit' key lru'
where lru' = lru { content = contents' }
contents' = adjust' (\v -> v {value = val}) key contents
add firstKey = if full then (lru'', Just (key, val))
else (lru', Nothing)
where
firstLV' = Link val Nothing $ Just firstKey
contents' = Map.insert key firstLV' .
adjust' (\v -> v { prev = Just key }) firstKey $
contents
lru' = lru { first = Just key, content = contents' }
Just lastKey = last lru'
Just lastLV = Map.lookup lastKey contents'
contents'' = Map.delete lastKey contents'
lru'' = delete' lastKey lru' contents'' lastLV
lookup :: Ord key => key -> LRU key val -> (LRU key val, Maybe val)
lookup key lru = case Map.lookup key $ content lru of
Nothing -> (lru, Nothing)
Just lv -> (hit' key lru, Just . value $ lv)
delete :: Ord key => key -> LRU key val -> (LRU key val, Maybe val)
delete key lru = maybe (lru, Nothing) delete'' mLV
where
delete'' lv = (delete' key lru cont' lv, Just $ value lv)
(mLV, cont') = Map.updateLookupWithKey (\_ _ -> Nothing) key $ content lru
pop :: Ord key => LRU key val -> (LRU key val, Maybe (key, val))
pop lru = if size lru == 0 then (lru, Nothing) else (lru', Just pair)
where
Just lastKey = last lru
(lru', Just lastVal) = delete lastKey lru
pair = (lastKey, lastVal)
size :: LRU key val -> Int
size = Map.size . content
hit' :: Ord key => key -> LRU key val -> LRU key val
hit' key lru = if key == firstKey then lru else notFirst
where Just firstKey = first lru
Just lastKey = last lru
Just lastLV = Map.lookup lastKey conts
conts = content lru
notFirst = if key == lastKey then replaceLast else replaceMiddle
adjFront = adjust' (\v -> v { prev = Just key}) firstKey .
adjust' (\v -> v { prev = Nothing
, next = first lru }) key
replaceLast = lru { first = Just key
, last = prev lastLV
, content = cLast
}
Just pKey = prev lastLV
cLast = adjust' (\v -> v { next = Nothing }) pKey . adjFront $ conts
replaceMiddle = lru { first = Just key
, content = cMid
}
Just keyLV = Map.lookup key conts
Just prevKey = prev keyLV
Just nextKey = next keyLV
cMid = adjust' (\v -> v { next = Just nextKey }) prevKey .
adjust' (\v -> v { prev = Just prevKey }) nextKey .
adjFront $ conts
delete' :: Ord key => key
-> LRU key val
-> Map key (LinkedVal key val)
-> LinkedVal key val
-> LRU key val
delete' key lru cont' lv = if Map.null cont' then deleteOnly else deleteOne
where
deleteOnly = lru { first = Nothing
, last = Nothing
, content = cont'
}
Just firstKey = first lru
deleteOne = if firstKey == key then deleteFirst else deleteNotFirst
deleteFirst = lru { first = next lv
, content = contFirst
}
Just nKey = next lv
contFirst = adjust' (\v -> v { prev = Nothing }) nKey cont'
Just lastKey = last lru
deleteNotFirst = if lastKey == key then deleteLast else deleteMid
deleteLast = lru { last = prev lv
, content = contLast
}
Just pKey = prev lv
contLast = adjust' (\v -> v { next = Nothing}) pKey cont'
deleteMid = lru { content = contMid }
contMid = adjust' (\v -> v { next = next lv }) pKey .
adjust' (\v -> v { prev = prev lv }) nKey $
cont'
adjust' :: Ord k => (a -> a) -> k -> Map k a -> Map k a
#if MIN_VERSION_containers(0,5,0)
adjust' = MapStrict.adjust
#else
adjust' f k m = Map.insertWith' (\_ o -> f o) k (error "adjust' used wrongly") m
#endif
valid :: Ord key => LRU key val -> Bool
valid lru = maybe True (fromIntegral (size lru) <=) (maxSize lru) &&
reverse orderedKeys == reverseKeys &&
size lru == length orderedKeys &&
all (`Map.member` contents) orderedKeys
where contents = content lru
orderedKeys = walk next . first $ lru
walk _ Nothing = []
walk f (Just k) = let Just k' = Map.lookup k contents
in k : (walk f . f $ k')
reverseKeys = walk prev . last $ lru