[Haskell-cafe] Spine-lazy "multiqueue"
Timothy Goddard
tim at goddard.net.nz
Tue Oct 21 21:40:05 EDT 2008
On Wed, 22 Oct 2008 11:54:50 Luke Palmer wrote:
> On Tue, Oct 21, 2008 at 3:02 PM, Justin Bailey <jgbailey at gmail.com> wrote:
> > On Tue, Oct 21, 2008 at 11:43 AM, Luke Palmer <lrpalmer at gmail.com> wrote:
> >> Hi, I need a rather strange data structure, and I can't find any
> >> existing implementations or think of a way to implement it. It's a
> >> "multiqueue", basically a map of queues. The trick is that it should
> >> be lazy in its spine and still support efficient access. For example,
> >> the following should hold:
> >
> > This doesn't answer your question, but how is a Map of queues not
> > "spine-lazy"? I'm mostly looking to understand that term.
>
> Well, first, my question was highly malformed. I actually just want a
> spine lazy map of lists; queues were not what I wanted.
>
> Data.Map is strict in its keys, meaning rougly that you cannot store
> infinitely many keys in a map. So:
>
> foldr (\x x -> Map.insert x x) Map.empty [0..] = _|_
>
> I.e. if you take this map that maps every natural to itself and try to
> do anything with it, you will get an infinite loop (or stack overflow,
> or whatever).
>
> On the other hand, the "map" type [(k,v)] *is* spine lazy, because, for
> example:
>
> lookup 42 [ (x,x) | x <- [0..] ] = Just 42
>
> It's just not very efficient. I'm basically looking for a version of
> the above which has a logarithmic lookup time.
>
> The best I've come up with so far is a binary search tree where the
> most recently inserted thing is at the root. It's not balanced,
> because balancing would make it strict (as far as I can tell). So
> it's only logarithmic time sometimes.
>
> Luke
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe
You might possibly be able to get a logarithmic lookup time for keys known to
be present while preserving some laziness (don't ask me how) but to say a key
does not exist in the map you would have to somehow check them all, which
with an infinite list of keys will never complete.
You're unlikely to get a free lunch with infinite maps - infinite items means
infinite depth to any tree structure and there are few other nice
alternatives.
You could simply add your own laziness - have a special map which consumes a
list of (key, value) pairs and where reading the map also returns another map
evaluated enough to answer the immediate query. Reading an already discovered
key will take logarithmic time while reading an undiscovered key will take as
long as it takes to find it in the list (for a nonexistent key, until memory
runs out).
You could also work carefully with mutable references inside the map to make
it appear pure from the outside. It could still present a referentially
transparent interface since it is only evaluating itself further, not
changing what it actually contains. You would have to make sure this worked
properly though.
With that map you could perform updates as normal. Reading a value from the
input list that already exists in the map would just do nothing.
I was interested enough to give this a try. Source is attached. It's
incomplete - if you finish it please send me the result. Otherwise, use as
you like.
Cheers,
Tim
-------------- next part --------------
module InfiniteMap
(
InfiniteMap,
fromList,
(!)
)
where
import System.IO.Unsafe
import Data.IORef
import qualified Data.Map as M
data InfiniteMap k v = InfiniteMap {imRef :: IORef ((M.Map k v), [(k, v)])}
fromList :: Ord k => [(k, v)] -> InfiniteMap k v
fromList l = InfiniteMap (unsafePerformIO $ newIORef (M.empty, l))
fillMapUntil :: (Ord k, Eq k) => k -> (M.Map k v, [(k, v)]) -> (M.Map k v, [(k, v)])
fillMapUntil tk (m, []) = (m, [])
fillMapUntil tk (m, ((k, v):xs))
| tk == k = (filledMap, xs)
| otherwise = fillMapUntil tk (filledMap, xs)
where
filledMap = M.insertWith' (\a _ -> a) k v m
(!) :: Ord k => InfiniteMap k v -> k -> v
m ! k = if k `M.member` cmap then (M.!) cmap k else (if k `M.member` newMap then (M.!) newMap k else error "Key not in map")
where
cmap = fst $ unsafePerformIO $ readIORef $ imRef m
newMap = unsafePerformIO $ do
(nm, nl) <- atomicModifyIORef (imRef m) (\a -> let res = fillMapUntil k a in (res, res))
return nm
insert :: Ord k => k -> v -> InfiniteMap k v -> InfiniteMap k v
insert k v m = InfiniteMap $ unsafePerformIO $ newIORef (M.insert k v cmap, clist)
where
(cmap, clist) = unsafePerformIO $ readIORef $ imRef m
More information about the Haskell-Cafe
mailing list