haskus-utils-0.6.0.0: src/lib/Haskus/Utils/STM/TList.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
-- | Transactional list
module Haskus.Utils.STM.TList
( TList
, TNode
, empty
, singleton
, null
, length
, first
, last
, prev
, next
, value
, deleteAll
, delete
, filter
, find
, append
, append_
, prepend
, prepend_
, insertBefore
, insertAfter
, toList
, toReverseList
, fromList
, index
, take
)
where
import Prelude hiding (null,length,last,filter,take)
import Haskus.Utils.STM
import Haskus.Utils.Flow
import Haskus.Utils.Maybe
-- | A double linked-list
newtype TList a = TList (TNode a)
-- | A node in the list
--
-- Every list has a marker node whose value is Nothing. Its nodePrev links to
-- the last node and its nodeNext links to the first node.
data TNode a = TNode
{ nodeValue :: Maybe a -- value (Nothing if list marker)
, nodePrev :: TVar (TNode a) -- previous node
, nodeNext :: TVar (TNode a) -- next node
}
-- | Get value associated with a node
value :: TNode a -> a
value node = case nodeValue node of
Just v -> v
Nothing -> error "TList: empty node value"
-- | Empty node singleton
empty :: STM (TList a)
empty = do
p <- newTVar undefined
n <- newTVar undefined
let node = TNode Nothing p n
-- initially the marker node refers to itself
writeTVar p node
writeTVar n node
return (TList node)
-- | Remove all the elements of the list (O(1))
deleteAll :: TList a -> STM ()
deleteAll (TList m) = do
-- make the marker node refer to itself
writeTVar (nodeNext m) m
writeTVar (nodePrev m) m
-- | Create a singleton list
singleton :: e -> STM (TList e)
singleton e = do
m <- empty
void $ append e m
return m
-- | Indicate if the list is empty
null :: TList e -> STM Bool
null (TList m) = do
h <- readTVar (nodeNext m)
-- if the list is empty, the marker node refers to itself, hence the value of
-- the nodeNext node is Nothing
return (isNothing (nodeValue h))
-- | Count the number of elements in the list (0(n))
length :: TList e -> STM Word
length (TList m) = go 0 m
where
go !n node = do
node' <- readTVar (nodeNext node)
case nodeValue node' of
Nothing -> return n
Just _ -> go (n+1) node'
-- | Get the first element if any
first :: TList e -> STM (Maybe (TNode e))
first (TList m) = next m
-- | Get the last element if any
last :: TList e -> STM (Maybe (TNode e))
last (TList m) = prev m
-- | Get the previous element if any
prev :: TNode a -> STM (Maybe (TNode a))
prev n = do
h <- readTVar (nodePrev n)
case nodeValue h of
Nothing -> return Nothing
Just _ -> return (Just h)
-- | Get the next element if any
next :: TNode a -> STM (Maybe (TNode a))
next n = do
h <- readTVar (nodeNext n)
case nodeValue h of
Nothing -> return Nothing
Just _ -> return (Just h)
-- | Delete a element of the list
delete :: TNode a -> STM ()
delete n = do
-- if somehow we delete the marker node, we get a ring-list
left <- readTVar $ nodePrev n
right <- readTVar $ nodeNext n
writeTVar (nodeNext left) right
writeTVar (nodePrev right) left
-- Link list node to itself so subsequent 'delete' calls will be harmless.
writeTVar (nodePrev n) n
writeTVar (nodeNext n) n
-- | Insert a node between two adjacent nodes.
insertBetween :: a -> TNode a -> TNode a -> STM (TNode a)
insertBetween v left right = do
n <- TNode (Just v) <$> newTVar left
<*> newTVar right
writeTVar (nodeNext left) n
writeTVar (nodePrev right) n
return n
-- | Append an element to the list
append :: a -> TList a -> STM (TNode a)
append v (TList m) = insertAfter v m
-- | Append an element to the list
append_ :: a -> TList a -> STM ()
append_ a = void . append a
-- | Prepend an element to the list
prepend :: a -> TList a -> STM (TNode a)
prepend v (TList m) = insertBefore v m
-- | Prepend an element to the list
prepend_ :: a -> TList a -> STM ()
prepend_ a = void . prepend a
-- | Insert an element before another
insertBefore :: a -> TNode a -> STM (TNode a)
insertBefore v n = do
right <- readTVar $ nodeNext n
insertBetween v n right
-- | Insert an element after another
insertAfter :: a -> TNode a -> STM (TNode a)
insertAfter v n = do
left <- readTVar $ nodePrev n
insertBetween v left n
-- | Convert into a list (O(n))
toList :: TList a -> STM [a]
toList (TList m) = go [] m
where
go !xs node = do
node' <- readTVar (nodePrev node)
case nodeValue node' of
Nothing -> return xs
Just x -> go (x:xs) node'
-- | Convert into a reversed list (O(n))
toReverseList :: TList a -> STM [a]
toReverseList (TList m) = go [] m
where
go !xs node = do
node' <- readTVar (nodeNext node)
case nodeValue node' of
Nothing -> return xs
Just x -> go (x:xs) node'
-- | Create from a list
fromList :: [e] -> STM (TList e)
fromList xs = do
s <- empty
forM_ xs (`append` s)
return s
-- | Only keep element matching the criterium
filter :: (e -> STM Bool) -> TList e -> STM ()
filter f (TList m) = go m
where
go node = do
node' <- readTVar (nodeNext node)
case nodeValue node' of
Nothing -> return ()
Just v -> do
p <- f v
if not p
then delete node' >> go node
else go node'
-- | Find the first node matching the predicate (if any)
find :: (e -> STM Bool) -> TList e -> STM (Maybe (TNode e))
find f (TList m) = go m
where
go node = do
node' <- readTVar (nodeNext node)
case nodeValue node' of
Nothing -> return Nothing
Just v -> do
p <- f v
if p
then return (Just node')
else go node'
-- | Get the node from its index
index :: Word -> TList e -> STM (Maybe (TNode e))
index n (TList m) = go n m
where
go !i node = do
node' <- readTVar (nodeNext node)
case nodeValue node' of
Nothing -> return Nothing
Just _
| i == 0 -> return (Just node')
| otherwise -> go (i-1) node'
-- | Take (and remove) up to n elements in the list (O(n))
take :: Word -> TList e -> STM [e]
take n l = index n l >>= \case
-- return the whole list
Nothing -> do
r <- toList l
deleteAll l
return r
-- build the list and remove elements at the same time
Just node -> go [] node
where
go !xs node' = do
case nodeValue node' of
Nothing -> return xs
Just x -> do
p <- readTVar (nodePrev node')
delete node'
go (x:xs) p