Haskell - efficient equivalent for hash-based dictionary [duplicate] - haskell

I'm looking for a monad-free, constant access query O(1) associative array.
Consider the hypothetical type:
data HT k v = ???
I want to construct an immutable structure once:
fromList :: Foldable t, Hashable k => t (k,v) -> HT k v
I want to subsequently query it repeatedly with constant time access::
lookup :: Hashable k => HT k v -> k -> Maybe v
There appears to be two candidate libraries which fall short:
unordered-containers
hashtables
unordered-containers
unordered-containers contains both strict and lazy variants of the type HashMap. Both HashMaps have O(log n) queries as documented by the lookup function. This query access time appears to be due to the construction of the HashMap types, which have an internal tree structure allowing for O(log n) insert functionality. An understandable design trade off for many use-cases, but since I don't need a mutable HashMap this tradeoff hampers my use-case.
hashtables
hashtables contains a HashTable type-class and three instance types with varying table constructions strategies. This library's type-class defines a constant time O(1) lookup function definition, but it is eternally embedded in the ST monad. There is no way to "freeze" the stateful HashTable implementations and have a lookup function that is not embedded of a stateful monad. The library's type-class interface is well designed when the entire computation is wrapped in a state monad, but this design is unsuitable for my use-case.
Does there exist some other library which defines types and functions which can construct an immutable constant access query O(1) associative array that is not embedded in a stateful monad?
Does there exist some way to wrap or modify these existing hashing-based libraries to produce an immutable constant access query O(1) associative array that is not embedded in a stateful monad?

The library you want is… unordered-containers. Or just plain old Data.Map from containers, if you’d prefer.
The note in the unordered-containers documentation explains why you shouldn’t worry about the O(log n) time complexity for lookups:
Many operations have a average-case complexity of O(log n). The implementation uses a large base (i.e. 16) so in practice these operations are constant time.
This is a common practice with certain kinds of functional data structures because it allows good sharing properties while also having good time complexities. log16 still produces very small numbers even for very large n, so you can almost always treat those complexities as “effectively constant time”.
If this is ever a bottleneck for your application, sure, go with something else, but I find that highly unlikely. After all, log16(1,000,000) is a little under 5, so your lookup time is not going to grow very quickly. Processing all that data is going to take up much more time than the overhead of the lookup.
As always: profile first. If you somehow have a problem that absolutely needs the fastest possible hash map in the world, you might need an imperative hash map, but for every case I’ve ever had, the functional ones work just fine.

You should follow Alexis' suggestion and use unordered-containers. If you really want something that is guaranteed to have Θ(1) lookups, you can define your own frozen version of any of the hash table types from hashtables using unsafePerformIO, but this is not very elegant. For example:
module HT
( HT
, fromList
, lookup
) where
import qualified Data.HashTable.IO as H
import Data.Hashable (Hashable)
import Data.Foldable (toList)
import System.IO.Unsafe (unsafePerformIO)
import Prelude hiding (lookup)
newtype HT k v = HT (H.BasicHashTable k v)
fromList :: (Foldable t, Eq k, Hashable k) => t (k, v) -> HT k v
fromList = HT . unsafePerformIO . H.fromList . toList
lookup :: (Eq k, Hashable k) => HT k v -> k -> Maybe v
lookup (HT h) k = unsafePerformIO $ H.lookup h k
Both uses of unsafePerformIO above should be safe. For that is crucial that the HT is exported as an abstract type.

Does there exist some other library which defines types and functions which can construct an immutable constant access query O(1) associative array that is not embedded in a stateful monad?
At this point in time, the answer is still no.
As of late-2019 there is an efficient IO-based hashtable package with decent benchmarks.
What you describe seems doable in the same way that pure, immutable Data.Array construction is possible. See Data.Array.Base for how this is achieved via unsafe* operators. A Data.Array is defined with a bound, and my initial thought is that a pure, immutable hashtable will potentially have GC problems if it's allowed to grow without bounds.

Related

Haskell: parallel computation and the 'sequential property' of monads

I am confused about why the REPA function computeP packs its result in a monad. It has the following type signature.
computeP :: (Load r1 sh e, Target r2 e, Source r2 e, Monad m) =>
Array r1 sh e -> m (Array r2 sh e)
In this tutorial it says
The reason for this is that monads give a well defined notion of sequence and thus computeP enforces completion of parallel evaluation in a particular point of monadic computations.
Likewise, this answer on Stack Overflow states that
The reason why parallel computation in Repa must be monadic has to do partially with lazyness, but mostly with Repa's inability to deal with nested parallelism. Sequential property of a Monad solves it for the most part[.]
Questions
What does having this 'sequential property' mean exactly?
How does a monad enforce this?
For the example of computeP: there is no constraint on which monad is used, so I could use the identity monad. Is it okay then to use the following function instead that just unpacks the monad, or will this give unexpected results because it lacks this sequential property? If it is okay, is there even a need to use a monad at all?
import Data.Functor.Identity
import Data.Array.Repa.Eval
import Data.Array.Repa
myComputeP :: (Load r1 sh e, Target r2 e, Source r2 e) => Array r1 sh e -> Array r2 sh e
myComputeP = runIdentity . computeP
Any help would be great.
This monad constraint is a heuristic trick. It helps disciplined users to avoid nested parallelism, but does nothing against malicious or clueless users.
Nested parallelism is the situation where, while computing some array in parallel, you end up having to compute another array in parallel. Repa does not support it (the reason is not important), so it tries to avoid it.
The type of computeP helps ensure that parallel computations are done in sequence with each other, but it is far from airtight; it is merely a "best effort" abstraction.
How does a monad enforce this?
Actually, computeP is only expected to work with monads whose bind (>>=) is strict in its first argument, so that in u >>= k, the function k will be applied only after u has been evaluated. Then if you use computeP with such a monad,
do w <- computeP v
k w
it is guaranteed that the vector w is evaluated before it gets passed to k, which may safely perform other computeP operations.
Examples of strict monads: IO, strict State, Maybe, [].
Examples of lazy monads: Identity, lazy State, Reader. (Lazy monads can be made strict, but not the other way around. In particular, you can define a strict identity monad if you want to do only Repa computations.)
To prevent nested parallelism, the type of computeP intentionally makes it cumbersome to use within operations that are likely to be done in parallel, such as map :: (a -> b) -> Array _ _ a -> Array _ _ b and fromFunction :: sh -> (sh -> a) -> Array _ _ a, which take non-monadic functions. One can still explicitly unwrap computeP, for example with runIdentity as you noticed: you can shoot yourself in the foot if you want, but it's on you to load the gun, point it down and pull the trigger.
Hopefully, that answers what is going on in Repa. What follows is a theoretical digression to answer this other question:
What does having this 'sequential property' mean exactly?
Those quotations are quite handwavy. As I see it, the relation between "sequentiality" and "monads" is two-fold.
First, for many monads in Haskell, the definition of (>>=) naturally dictates an order of evaluation, typically because it immediately pattern-matches on the first argument. As explained earlier, that's what Repa relies on to enforce that computeP computations happen in sequence (and that's why it would break if you specialize it to Identity; it is not a strict monad). In the grand scheme of things, this is a rather minor detail of lazy evaluation, rather than anything proper to monads in general.
Second, one core idea of pure functional programming is first-class computations, with explicit definitions of effects and composition. In this case, the effect is the parallel evaluation of vectors, and the composition we care about is sequential composition. Monads provide a general model, or interface, for sequential composition. That's another part of the reason why monads help solve the problem of avoiding nested parallelism in Repa.
The point is not that monads have an inherently sequential aspect, but it is that sequential composition is inherently monadic: if you try to list general properties that you would expect from anything worthy of the name "sequential composition", you're likely to end up with properties that together are called "monad"; that's one of the points of Moggi's seminal paper "Notions of computation and monads".
"Monads" is not a magical concept, rather it's enormously general so that lots of things happen to be monads. After all, the main requirement is that there's an associative operation; that's a pretty reasonable assumption to make about sequential composition. (If, when you hear "associativity", you think "monoid" or "category", note that all of these concepts are unified under the umbrella of "monoid objects", so as far as "associativity" is concerned, they're all the same idea, just in different categories.)

GHC performance implications of parametric types

I'll motivate the general question with a more specific one:
In GHC Haskell, should a Cofree [] a have the same performance as a containers-style Data.Tree a? Or does the additional polymorphism result in some kind of runtime cost?
Generally speaking, is there additional runtime cost associated with increasing "arity" of a type's kind?
I think a more classic concrete example would be something like vectors or arrays. The vector package exports both "boxed" and "unboxed" vectors. While boxed vectors can contain any Haskell type (including functions), unboxed vectors requires its elements to be an instance of the Unbox type class. Although this implies a more efficient packed memory representation without pointer indirections, you can't define a Functor instance for unboxed vectors anymore, so it comes with a loss of generality.
If you made use of
fmap :: (a -> b) -> Vector a -> Vector b
in a function of type
f :: Functor f => f SomeType -> f SomeOtherType
a "Dictionary", that is, a record with the corresponding fmap implementation will be passed at run-time as an additional implicit argument. You can actually see this by looking at the "Core" output produced by GHC, using the -ddump-simpl flag. Specifically, the arity of f above would be two instead of one.
In some cases, GHC can optimize this overhead away by creating specialized versions of your functions. You can help out via using SPECIALIZE/INLINABLE/... pragmas, using explicit export lists, maybe adding some strictness, and a few other tweaks which are also described in the documentation.
Regarding the overhead of using parametrically polymorphic types, it of course depends. My personal worst case was a factor 100 in an inner numeric loop (which was resolved by adding one SPECIALIZE pragma), so it can indeed bite you. Luckily, using the profiling tools and remembering that dictionaries influence a function's arity, tracking these issues down becomes very systematic.

Does a useful Haskell HashMap/HashTable/Dictionary library exist?

I'm looking for a monad-free, constant access query O(1) associative array.
Consider the hypothetical type:
data HT k v = ???
I want to construct an immutable structure once:
fromList :: Foldable t, Hashable k => t (k,v) -> HT k v
I want to subsequently query it repeatedly with constant time access::
lookup :: Hashable k => HT k v -> k -> Maybe v
There appears to be two candidate libraries which fall short:
unordered-containers
hashtables
unordered-containers
unordered-containers contains both strict and lazy variants of the type HashMap. Both HashMaps have O(log n) queries as documented by the lookup function. This query access time appears to be due to the construction of the HashMap types, which have an internal tree structure allowing for O(log n) insert functionality. An understandable design trade off for many use-cases, but since I don't need a mutable HashMap this tradeoff hampers my use-case.
hashtables
hashtables contains a HashTable type-class and three instance types with varying table constructions strategies. This library's type-class defines a constant time O(1) lookup function definition, but it is eternally embedded in the ST monad. There is no way to "freeze" the stateful HashTable implementations and have a lookup function that is not embedded of a stateful monad. The library's type-class interface is well designed when the entire computation is wrapped in a state monad, but this design is unsuitable for my use-case.
Does there exist some other library which defines types and functions which can construct an immutable constant access query O(1) associative array that is not embedded in a stateful monad?
Does there exist some way to wrap or modify these existing hashing-based libraries to produce an immutable constant access query O(1) associative array that is not embedded in a stateful monad?
The library you want is… unordered-containers. Or just plain old Data.Map from containers, if you’d prefer.
The note in the unordered-containers documentation explains why you shouldn’t worry about the O(log n) time complexity for lookups:
Many operations have a average-case complexity of O(log n). The implementation uses a large base (i.e. 16) so in practice these operations are constant time.
This is a common practice with certain kinds of functional data structures because it allows good sharing properties while also having good time complexities. log16 still produces very small numbers even for very large n, so you can almost always treat those complexities as “effectively constant time”.
If this is ever a bottleneck for your application, sure, go with something else, but I find that highly unlikely. After all, log16(1,000,000) is a little under 5, so your lookup time is not going to grow very quickly. Processing all that data is going to take up much more time than the overhead of the lookup.
As always: profile first. If you somehow have a problem that absolutely needs the fastest possible hash map in the world, you might need an imperative hash map, but for every case I’ve ever had, the functional ones work just fine.
You should follow Alexis' suggestion and use unordered-containers. If you really want something that is guaranteed to have Θ(1) lookups, you can define your own frozen version of any of the hash table types from hashtables using unsafePerformIO, but this is not very elegant. For example:
module HT
( HT
, fromList
, lookup
) where
import qualified Data.HashTable.IO as H
import Data.Hashable (Hashable)
import Data.Foldable (toList)
import System.IO.Unsafe (unsafePerformIO)
import Prelude hiding (lookup)
newtype HT k v = HT (H.BasicHashTable k v)
fromList :: (Foldable t, Eq k, Hashable k) => t (k, v) -> HT k v
fromList = HT . unsafePerformIO . H.fromList . toList
lookup :: (Eq k, Hashable k) => HT k v -> k -> Maybe v
lookup (HT h) k = unsafePerformIO $ H.lookup h k
Both uses of unsafePerformIO above should be safe. For that is crucial that the HT is exported as an abstract type.
Does there exist some other library which defines types and functions which can construct an immutable constant access query O(1) associative array that is not embedded in a stateful monad?
At this point in time, the answer is still no.
As of late-2019 there is an efficient IO-based hashtable package with decent benchmarks.
What you describe seems doable in the same way that pure, immutable Data.Array construction is possible. See Data.Array.Base for how this is achieved via unsafe* operators. A Data.Array is defined with a bound, and my initial thought is that a pure, immutable hashtable will potentially have GC problems if it's allowed to grow without bounds.

Memoize a Double function in Haskell

I have a function
slow :: Double -> Double
which gets called very often (hundreds of millions of times), but only gets called on about a thousand discrete values. This seems like an excellent candidate for memoization, but I can't figure out how to memoize a function of a Double.
The standard technique of making a list doesn't work, since it's not an integral type. I looked at Data.MemoCombinators, but it doesn't natively support Doubles. There was a bits function for handling more data types, but Double isn't an instance of Data.Bits.
Is there an elegant way to memoize `slow?
You could always use ugly-memo. The internals are impure, but it's fast and does what you need (except if the argument is NaN).
I think StableMemo should do exactly what you want, but I don't have any experience with that.
There are two main approaches: use the Ord property to store the keys in a tree structure, like Map. That doesn't require the integral property you'd need for e.g. a MemoTrie approach; it is thus slower but very simple.
The alternative, that works with yet much more general types, is to map unorderedly onto a large integral domain with a Hash function, in order to, well, store the keys in a hash map. This is going to be substantially faster but pretty much just as simple since the interface of HashMap largely matches that of Map, so you probably want to go that way.
Now, sadly neither is quite as simple to use as MemoCombinators. That builds directly on IntTrie, which is specialised for offering a lazy / infinite / pure interface. Both Map and particularly HashMap, in contrast, can be used very well for impure memoisation, but are not inherently able to do it purely. You may throw in some UnsafePerformIO (uh oh), or just do it openly in the IO monad (yuk!). Or use StableMemo.
But it's actually easy and safe if you already know which values it's going to be, at least most of the calls, at compile-time. Then you can just fill a local hash map with those values in the beginning, and at each call look up if it's there and otherwise simply call the expensive function directly:
import qualified Data.HashMap.Lazy as HM
type X = Double -- could really be anything else
notThatSlow :: Double -> X
notThatSlow = \v -> case HM.lookup v memo of
Just x -> x
Nothing -> slow v
where memo = HM.fromList [ (v, x) | v<-expectedValues, let x = slow v ]

How can I ensure amortized O(n) concatenation from Data.Vector?

I have an application where it is efficient to use Vectors for one part of the code. However, during the computation I need to keep track of some of the elements. I have heard that you can get O(n) amortized concatenation from Data.Vectors (by the usual array growing trick) but I think that I am not doing it right. So lets say we have the following setup:
import Data.Vector((++),Vector)
import Prelude hiding ((++))
import Control.Monad.State.Strict
data App = S (Vector Int)
add :: Vector Int -> State App ()
add v1 = modify (\S v2 -> S (v2 ++ v1))
Does add run in amortized O(n) time in this case? If not, how can I make add do that (do I need to store an (forall s. MVector s Int) in the State?). Is there a more efficient way of implementing add?
I don't know the vector library well either, so I have to stick to general principles mostly. Benchmark it, run a sequence of adds similar to what you expect in your application and see what performance you get. If it's 'good enough', great, stick with the simple code. If not, before storing a (forall s. MVector s Int) in the state (which you can't directly, tuples can't hold forall-types, so you'd need to wrap it), try improving the add-behaviour by converting to mutable vectors and perform the concatenation there before freezing to get an immutable vector again, roughly
add v1 = do
S v2 <- get
let v3 = runST $ do
m1 <- unsafeThaw v2
m2 <- unsafeGrow m1 (length v1)
-- copy contents of v1 behind contents of v2
unsafeFreeze m2
put (S v3)
You may need to insert some strictness there. However, if unsafeGrow needs to copy, that will not guarantee amortized O(n) behaviour.
You can get amortized O(n) behaviour by
storing the number of used slots in the state too
if the new vector fits in the free space at the end, thaw, copy, freeze without growing
if it doesn't fit in the free space, grow by at least a factor of 2, that guarantees that each element is copied at most twice on average

Resources