Skip to content

Instantly share code, notes, and snippets.

@chessai
Created October 10, 2018 03:36
Show Gist options
  • Save chessai/dc619249589e73bb7ed2083832dc4a1f to your computer and use it in GitHub Desktop.
Save chessai/dc619249589e73bb7ed2083832dc4a1f to your computer and use it in GitHub Desktop.
-- Copyright (c) 2014, Ryan Trinkle
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this
-- list of conditions and the following disclaimer.
--
-- * Redistributions in binary form must reproduce the above copyright notice,
-- this list of conditions and the following disclaimer in the documentation
-- and/or other materials provided with the distribution.
--
-- * Neither the name of the {organization} nor the names of its
-- contributors may be used to endorse or promote products derived from
-- this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | This module defines the 'WeakBag' type, which represents a mutable
-- collection of items that does not cause the items to be retained in memory.
-- It is useful for situations where a value needs to be inspected or modified
-- if it is still alive, but can be ignored if it is dead.
module Data.WeakBag
( WeakBag
, WeakBagTicket
, insert
, empty
, isEmpty
, singleton
, traverse
, remove
) where
import Prelude hiding (traverse)
import Control.Monad (forM_)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Exception (evaluate)
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IntMap
import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
import System.Mem.Weak (Weak, mkWeakPtr, finalize, deRefWeak)
-- | A @WeakBag a@ holds a set of values of type @a@, but does not retain them -
-- that is, they can still be garbage-collected. As long as the @a@s remain
-- alive, the 'WeakBag' will continue to refer to them.
data WeakBag a = WeakBag
{-# UNPACK #-} !(IORef Int)
{-# UNPACK #-} !(IORef (IntMap (Weak a)))
-- | When inserting an item into a 'WeakBag', a 'WeakBagTicket' is returned.
-- If the caller retains the ticket, the item is guaranteed to stay in memory (and
-- thus in the 'WeakBag'). The ticket can also be used to remove the item from
-- the 'WeakBag' prematurely (i.e. while it is still alive), using 'remove'.
data WeakBagTicket a = WeakBagTicket
{-# UNPACK #-} !(Weak a)
!a
-- | Insert an item into a 'WeakBag'.
insert ::
a
-> WeakBag a
-> IO (WeakBagTicket a)
{-# INLINE insert #-}
insert a (WeakBag nextId children) = {-# SCC "insert" #-} do
a' <- evaluate a
myId <- atomicModifyIORef' nextId $ \n -> (succ n, n)
let cleanup = atomicModifyIORef' children $ \cs -> (IntMap.delete myId cs, ())
wa <- mkWeakPtr a' $ Just cleanup
atomicModifyIORef' children $ \cs -> (IntMap.insert myId wa cs, ())
return $ WeakBagTicket wa a'
-- | Create an empty 'WeakBag'.
empty :: IO (WeakBag a)
{-# INLINE empty #-}
empty = {-# SCC "empty" #-} do
nextId <- newIORef 1
children <- newIORef IntMap.empty
return $ WeakBag nextId children
-- | Check whether a 'WeakBag' is empty.
isEmpty :: WeakBag a -> IO Bool
{-# INLINE isEmpty #-}
isEmpty (WeakBag _ children) = {-# SCC "isEmpty" #-} IntMap.null <$> readIORef children
singleton :: a -> IO (WeakBag a, WeakBagTicket a)
{-# INLINE singleton #-}
singleton a = {-# SCC "singleton" #-} do
bag <- empty
ticket <- insert a bag
return (bag, ticket)
-- | Visit every node in the given list. If new nodes are appended during the
-- traversal, they will not be visited. Every live node that was in the list
-- when the traversal began will be visited exactly once; however, no guarantee
-- is made about the order of the traversal.
traverse :: forall a m. MonadIO m => WeakBag a -> (a -> m ()) -> m ()
{-# INLINE traverse #-}
traverse (WeakBag _ children) f = {-# SCC "traverse" #-} do
cs <- liftIO $ readIORef children
forM_ cs $ \c -> do
ma <- liftIO $ deRefWeak c
mapM_ f ma
remove :: WeakBagTicket a -> IO ()
{-# INLINE remove #-}
remove (WeakBagTicket w _) = {-# SCC "remove" #-} finalize w
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment