lvish-1.1.1.1: Data/LVar/PureMap/Unsafe.hs
{-# LANGUAGE Unsafe #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables, ConstraintKinds #-}
module Data.LVar.PureMap.Unsafe
(
-- * Unsafe operations:
unsafePeekKey,
-- unsafeGetOrInit, unsafeInsertIfAbsent,
-- * These are here only to reexport downstream:
IMap(..), forEachHP
)
where
import Control.LVish.DeepFrz.Internal
import Control.LVish
import Control.LVish.Internal as LI
import Control.LVish.SchedIdempotent (freezeLV)
import qualified Control.LVish.SchedIdempotent as L
import Data.LVar.Generic as G
import Data.LVar.Generic.Internal (unsafeCoerceLVar)
import Data.UtilInternal (traverseWithKey_)
import Control.Applicative ((<$>))
import Data.IORef
import qualified Data.Foldable as F
import qualified Data.Map.Strict as M
import Data.List (intersperse)
import System.IO.Unsafe (unsafeDupablePerformIO)
------------------------------------------------------------------------------
-- IMaps implemented on top of LVars:
------------------------------------------------------------------------------
-- | The map datatype itself. Like all other LVars, it has an @s@ parameter (think
-- `STRef`) in addition to the @a@ parameter that describes the type of elements
-- in the set.
--
-- Performance note: There is only /one/ mutable location in this implementation. Thus
-- it is not a scalable implementation.
newtype IMap k s v = IMap (LVar s (IORef (M.Map k v)) (k,v))
-- | Equality is physical equality, as with @IORef@s.
instance Eq (IMap k s v) where
IMap lv1 == IMap lv2 = state lv1 == state lv2
-- | An `IMap` can be treated as a generic container LVar. However, the polymorphic
-- operations are less useful than the monomorphic ones exposed by this module.
instance LVarData1 (IMap k) where
freeze orig@(IMap (WrapLVar lv)) = WrapPar$ do freezeLV lv; return (unsafeCoerceLVar orig)
-- Unlike the Map-specific forEach variants, this takes only values, not keys.
addHandler mh mp fn = forEachHP mh mp (\ _k v -> fn v)
sortFrzn (IMap lv) = AFoldable$ unsafeDupablePerformIO (readIORef (state lv))
-- | The `IMap`s in this module also have the special property that they support an
-- /O(1)/ freeze operation which immediately yields a `Foldable` container
-- (`snapFreeze`).
instance OrderedLVarData1 (IMap k) where
snapFreeze is = unsafeCoerceLVar <$> freeze is
-- As with all LVars, after freezing, map elements can be consumed. In
-- the case of this `IMap` implementation, it need only be `Frzn`, not
-- `Trvrsbl`.
instance F.Foldable (IMap k Frzn) where
foldr fn zer (IMap lv) =
let set = unsafeDupablePerformIO (readIORef (state lv)) in
F.foldr fn zer set
-- Of course, the stronger `Trvrsbl` state is still fine for folding.
instance F.Foldable (IMap k Trvrsbl) where
foldr fn zer mp = F.foldr fn zer (castFrzn mp)
-- `IMap` values can be returned as the result of a
-- `runParThenFreeze`. Hence they need a `DeepFrz` instance.
-- @DeepFrz@ is just a type-coercion. No bits flipped at runtime.
instance DeepFrz a => DeepFrz (IMap k s a) where
type FrzType (IMap k s a) = IMap k Frzn (FrzType a)
frz = unsafeCoerceLVar
instance (Show k, Show a) => Show (IMap k Frzn a) where
show (IMap lv) =
let mp' = unsafeDupablePerformIO (readIORef (state lv)) in
"{IMap: " ++
(concat $ intersperse ", " $ map show $
M.toList mp') ++ "}"
-- | For convenience only; the user could define this.
instance (Show k, Show a) => Show (IMap k Trvrsbl a) where
show lv = show (castFrzn lv)
-- | Add an (asynchronous) callback that listens for all new key/value pairs added to
-- the map, optionally enrolled in a handler pool.
forEachHP :: Maybe HandlerPool -- ^ optional pool to enroll in
-> IMap k s v -- ^ Map to listen to
-> (k -> v -> Par d s ()) -- ^ callback
-> Par d s ()
forEachHP mh (IMap (WrapLVar lv)) callb = WrapPar $ do
L.addHandler mh lv globalCB deltaCB
return ()
where
deltaCB (k,v) = return$ Just$ unWrapPar $ callb k v
globalCB ref = do
mp <- L.liftIO $ readIORef ref -- Snapshot
unWrapPar $
traverseWithKey_ (\ k v -> forkHP mh$ callb k v) mp
------------------------------------------------------------------------------
-- | An unsafe, nonblocking version of `getKey`. This reveals whether
unsafePeekKey :: Ord k => k -> IMap k s v -> Par d s (Maybe v)
unsafePeekKey key (IMap (WrapLVar lv)) = do
mp <- liftIO$ readIORef (L.state lv)
return$! M.lookup key mp
-- | A generic initialize proceedure that returns a preexisting value, if it exists,
-- otherwise filling in a new "bottom" value and returning it.
--
-- The boolean return value is @True@ iff a new, fresh entry was created.
unsafeGetOrInit :: forall f a b d s key . (Ord key, LVarWBottom f, LVContents f a, Show key, Ord a) =>
key -- ^ The key to lookup or populate.
-> IMap key s (f s a)
-> Par d s (Bool, f s a)
unsafeGetOrInit key (IMap (WrapLVar lv)) = go1
where
-- go1 is OPTIONAL optimization. Could skip right to go2.
-- The tension here is that we can't do IO during an atomicModifyIORef.
go1 = do
let mpref = (L.state lv)
mp <- liftIO$ readIORef mpref
case M.lookup key mp of
Just x -> return (False,x)
Nothing -> go2
go2 = do
bot <- G.newBottom
liftIO$ atomicModifyIORef' (L.state lv) $ \ mp ->
-- Here we pay the cost of a SECOND lookup. Ouch!
case M.lookup key mp of
Nothing -> (M.insert key bot mp,(True,bot))
-- Oops! it appeared in the meantime. Our allocation was still wasted:
Just x -> (mp,(False,x))
-- FIXME: need a delta-thresh!
-- act <- putLV_ (unWrapLVar lv) putter
{-
-- | An unsafe way to race to insert. Returns Nothing if the insert is successful,
-- and the found value otherwise.
unsafeInsertIfAbsent :: Ord k => k -> v -> IMap k s v -> Par d s (Maybe v)a
unsafeInsertIfAbsent key val (IMap (WrapLVar lv)) = liftIO$
atomicModifyIORef' (L.state lv) $ \ mp ->
case M.lookup key mp of
Nothing -> (M.insert key val mp,Nothing)
-- Oops! it appeared in the meantime. Our allocation was still wasted:
x@(Just _) -> (mp,x)
-- FIXME: need a delta-thresh!
-- act <- putLV_ (unWrapLVar lv) putter
-}