haskellunordered-containers

How can I inspect a hidden HashMap.Base type?


I'm working on a project that references unordered-containers-0.2.8.0:Data.HashMap.Base.HashMap.

The module containing value of that type does not state where it came from in import sections and I cannot import Data.HashMap.Base.

However :browsing suggests that the type is intended to be abstract at least in some cases.

> :bro Data.HashMap.Lazy
[...]
unordered-containers-0.2.8.0:Data.HashMap.Base.toList ::
 unordered-containers-0.2.8.0:Data.HashMap.Base.HashMap k v
 -> [(k, v)]

Does it mean I can import functions from either Lazy or Strict variants?


Solution

  • Yes, in the unordered-containers package, the intention is that you either import Data.HashMap.Lazy or Data.HashMap.Strict. Both variants have strict keys (evaluated to WHNF) and differ in whether values are also evaluated to WHNF or not.

    In the source package for unordered-containers, there is a hidden module Data.HashMap.Base that contains code shared by both the lazy and strict variants. Because this module is hidden (technically, because it is listed in the package's Cabal file under the other-modules stanza instead of the exposed-modules stanza), it is not intended to be imported directly, but only indirectly via one of the other two modules.

    For toList in particular, the exposed modules Data.HashMap.Lazy and Data.HashMap.Strict both use (i.e., re-export) the same definition of toList defined in the hidden module Data.HashMap.Base, which is why you're seeing this in your GHCI browsing.

    If you import one of the modules in GHCI, then you should be able to inspect toList and its component types:

    > import Data.HashMap.Strict
    > :t toList
    toList :: HashMap k v -> [(k, v)]
    > :i HashMap
    type role HashMap nominal representational
    data HashMap k v
      = unordered-containers-0.2.7.2:Data.HashMap.Base.Empty
      ...
    

    In fact, you'll discover that the same HashMap definition is used for both strict and lazy variants, so it's not an "abstract" type in the sense you're thinking: both implementations use the same underlying concrete data structure and differ in how the functions operating on that data structure use it.

    This is true for the Data.Map.Strict and Data.Map.Lazy plain maps, too, by the way. A "strict" map can be used with lazy operations and vice versa, because "strict" and "lazy" maps are the same object. Quoting from the documentation for Data.Map.Lazy:

    API of this module is strict in the keys, but lazy in the values. If you need value-strict maps, use Data.Map.Strict instead. The Map type itself is shared between the lazy and strict modules, meaning that the same Map value can be passed to functions in both modules (although that is rarely needed).