1{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE DeriveFunctor #-}
4{-# LANGUAGE DeriveFoldable #-}
5{-# LANGUAGE DeriveTraversable #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE LambdaCase #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE TypeFamilies #-}
12{-# LANGUAGE ForeignFunctionInterface #-}
13{-# LANGUAGE JavaScriptFFI #-}
14{-# LANGUAGE MagicHash #-}
15{-# LANGUAGE UnliftedFFITypes #-}
17module Data.FastMutableIntMap
29 , traverseIntMapPatchWithKey
31 , forIntersectionWithImmutable_
33 , patchIntMapNewElements
34 , patchIntMapNewElementsMap
38--TODO: Pure JS version
39--TODO: Fast copy to FastIntMap
40--TODO: Fast patch type
42import Prelude hiding (lookup)
44import Data.IntMap.Strict (IntMap)
45import qualified Data.IntMap.Strict as IntMap
47import Reflex.Patch.Class
48import Control.Monad.IO.Class
51import Control.Lens (imapM_, iforM_, iforM)
55import JavaScript.Array (JSArray)
56import qualified JavaScript.Array as JSArray
57import GHCJS.Prim (JSVal (..))
58import GHC.Exts (ByteArray#)
59import GHC.Prim (unsafeCoerce#)
62import Data.Foldable (traverse_)
65newtype FastMutableIntMap a =
67 FastMutableIntMap JSVal
69 FastMutableIntMap (IORef (IntMap a))
72new :: IntMap a -> IO (FastMutableIntMap a)
76 imapM_ (insert result) m
79new m = FastMutableIntMap <$> newIORef m
83foreign import javascript unsafe "var a = []; a.__ghcjsArray = true; $r = { f: { n: 'FastMutableIntMap', size: 2 }, d1: a, d2: { d1: 0 }, m: 0 }" newEmpty :: IO (FastMutableIntMap a)
85newEmpty :: IO (FastMutableIntMap a)
86newEmpty = FastMutableIntMap <$> newIORef IntMap.empty
89insert :: forall a. FastMutableIntMap a -> Int -> a -> IO ()
91insert m k !v = insert_ m k ((unsafeCoerce# :: a -> ByteArray#) v)
92foreign import javascript unsafe "if($1.d1[$2 & 0x7FFFFFFF] === undefined) { ++$1.d2.d1; } $1.d1[$2 & 0x7FFFFFFF] = $3;" insert_ :: FastMutableIntMap a -> Int -> ByteArray# -> IO ()
94insert (FastMutableIntMap r) k v = modifyIORef' r $ IntMap.insert k v
97insertLookup :: forall a. FastMutableIntMap a -> Int -> a -> IO (Maybe a)
99insertLookup m k !newVal = do
100 oldVal <- insertLookup_ m k ((unsafeCoerce# :: a -> ByteArray#) newVal)
101 return $ elemFromJSVal oldVal
102foreign import javascript unsafe "$r = $1.d1[$2 & 0x7FFFFFFF]; if($r === undefined) { ++$1.d2.d1; } $1.d1[$2 & 0x7FFFFFFF] = $3;" insertLookup_ :: FastMutableIntMap a -> Int -> ByteArray# -> IO JSVal
104insertLookup (FastMutableIntMap r) k v = do
105 oldMap <- readIORef r
106 let (oldVal, newMap) = IntMap.updateLookupWithKey (\_ _ -> Just v) k oldMap
107 writeIORef r $! newMap
111deleteLookup :: FastMutableIntMap a -> Int -> IO (Maybe a)
114 v <- deleteLookup_ m k
115 return $ elemFromJSVal v
116foreign import javascript unsafe "$r = $1.d1[$2 & 0x7FFFFFFF]; if($r !== undefined) { --$1.d2.d1; } delete $1.d1[$2 & 0x7FFFFFFF];" deleteLookup_ :: FastMutableIntMap a -> Int -> IO JSVal
118deleteLookup (FastMutableIntMap r) k = do
119 oldMap <- readIORef r
120 let (oldVal, newMap) = IntMap.updateLookupWithKey (\_ _ -> Nothing) k oldMap
121 writeIORef r $! newMap
125lookup :: forall a. FastMutableIntMap a -> Int -> IO (Maybe a)
129 return $ elemFromJSVal v
130foreign import javascript unsafe "$r = $1.d1[$2 & 0x7FFFFFFF];" lookup_ :: FastMutableIntMap a -> Int -> IO JS
132--TODO: Find out whether it's possible for Haskell objects to be represented as 'undefined'; if so, we need to do something slightly better to represent missing values
134elemFromJSVal :: JSVal -> Maybe a
135elemFromJSVal v = case isUndefined v of
137 False -> Just $ (unsafeCoerce# :: ByteArray# -> a) (case v of JSVal raw -> raw)
140lookup (FastMutableIntMap r) k = IntMap.lookup k <$> readIORef r
143--TODO: Do something faster than trying every one
144{-# INLINE forIntersectionWithImmutable_ #-}
145forIntersectionWithImmutable_ :: MonadIO m => FastMutableIntMap a -> IntMap b -> (a -> b -> m ()) -> m ()
147forIntersectionWithImmutable_ ma mb f = iforM_ mb $ \k b -> do
148 liftIO (lookup ma k) >>= \case
152forIntersectionWithImmutable_ (FastMutableIntMap r) b f = do
153 a <- liftIO $ readIORef r
154 traverse_ (uncurry f) $ IntMap.intersectionWith (,) a b
158for_ :: MonadIO m => FastMutableIntMap a -> (a -> m ()) -> m ()
161 raw <- liftIO $ packFastMutableIntMap m --TODO: Don't pack the keys, since we don't need them
162 forM_ (JSArray.toList raw) $ \p -> do
163 (_ :: Int, JSVal v) <- liftIO $ fromJSValUnchecked p
164 f $ (unsafeCoerce# :: ByteArray# -> a) v
166for_ (FastMutableIntMap r) f = do
167 a <- liftIO $ readIORef r
172foreign import javascript unsafe "$r = $1.d2.d1 === 0;" isEmpty :: FastMutableIntMap a -> IO Bool
174isEmpty :: FastMutableIntMap a -> IO Bool
175isEmpty (FastMutableIntMap r) = IntMap.null <$> readIORef r
179foreign import javascript unsafe "$r = $1.d2.d1;" size :: FastMutableIntMap a -> IO Int
181size :: FastMutableIntMap a -> IO Int
182size (FastMutableIntMap r) = IntMap.size <$> readIORef r
185-- | Make an immutable snapshot of the datastructure and clear it
186getFrozenAndClear :: FastMutableIntMap a -> IO (IntMap a)
188getFrozenAndClear m = do
189 raw <- packFastMutableIntMap m
191 pairs <- forM (JSArray.toList raw) $ \p -> do
192 (k, JSVal v) <- fromJSValUnchecked p
193 return (k, (unsafeCoerce# :: ByteArray# -> a) v)
194 return $ IntMap.fromDistinctAscList pairs
195-- | Make a copy of a FastMutableIntMap's contents, as an array of key/value pairs
196foreign import javascript unsafe "$r = new Array($1.d2.d1); $r.__ghcjsArray = true; var i = 0; for(var k in $1.d1) { if(k !== '__ghcjsArray') { var p = [k, $1.d1[k]]; p.__ghcjsArray = true; $r[i++] = p; } } $r.sort(function(a, b) { return a[0] - b[0]; });" packFastMutableIntMap :: FastMutableIntMap a -> IO JSArray
197foreign import javascript unsafe "$1.d1 = []; $1.d1.__ghcjsArray = true; $1.d2.d1 = 0;" clear :: FastMutableIntMap a -> IO ()
198--NOTE: We would like to use Array.forEach here, but, at least on Chrome 59, forEach appears to be linear in the maximum key in the array. Since we sometimes use very large indexes, this is unusable. for(... in ...), on the other hand, appears to actually operate sparsely - but this requires us to sort the array in a second step.
200getFrozenAndClear (FastMutableIntMap r) = do
201 result <- readIORef r
202 writeIORef r IntMap.empty
206applyPatch :: FastMutableIntMap a -> PatchIntMap a -> IO (IntMap a)
208applyPatch f (PatchIntMap m) = fmap (IntMap.mapMaybe id) $ iforM m $ \k mv -> case mv of
209 Nothing -> deleteLookup f k
210 Just v -> insertLookup f k v
212applyPatch (FastMutableIntMap r) p@(PatchIntMap m) = do
214 writeIORef r $! applyAlways p v
215 return $ IntMap.intersection v m
218newtype PatchIntMap a = PatchIntMap (IntMap (Maybe a)) deriving (Functor, Foldable, Traversable, Monoid)
220instance Patch (PatchIntMap a) where
221 type PatchTarget (PatchIntMap a) = IntMap a
222 apply (PatchIntMap p) v = if IntMap.null p then Nothing else Just $
223 let removes = IntMap.filter isNothing p
224 adds = IntMap.mapMaybe id p
225 in IntMap.union adds $ v `IntMap.difference` removes
227traverseIntMapPatchWithKey :: Applicative t => (Int -> a -> t b) -> PatchIntMap a -> t (PatchIntMap b)
228traverseIntMapPatchWithKey f (PatchIntMap m) = PatchIntMap <$> IntMap.traverseWithKey (\k mv -> traverse (f k) mv) m
230patchIntMapNewElements :: PatchIntMap a -> [a]
231patchIntMapNewElements (PatchIntMap m) = catMaybes $ IntMap.elems m
233patchIntMapNewElementsMap :: PatchIntMap a -> IntMap a
234patchIntMapNewElementsMap (PatchIntMap m) = IntMap.mapMaybe id m
236getDeletions :: PatchIntMap v -> IntMap v' -> IntMap v'
237getDeletions (PatchIntMap m) v = IntMap.intersection v m
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list