How to create a hashtbl using Base?

I cannot for the life of me figure out how to create and use a hashtbl using the Base library. There are some resources online concerning the standard library hashtbls, but I am not sure if the Base hashtbls are supposed to work the same way because the docs don’t seem to concretely define the type provided as an argument to the create function (or I could be using the docs wrong because I’m a beginner). I have also tried to follow Ch. 13 of RWO, and did this:

Hashtbl.create ~hashable:String.hash ()

But I get the error:

Error: The function applied to this argument has type
         ?growth_allowed:bool ->
         ?size:int -> unit -> ('a, 'b) Base__Hashtbl.t
This argument cannot be applied with label ~hashable

Again, because I don’t know what the type of the arguments are, I don’t even know if Base still uses the ~hashable optional argument. Has the interface changed?

1 Like

This is one of the few differences between the Base API and that of Core and Core_kernel. The idiom of Base is as follows:

>>> let table = Hashtbl.create (module Int) ();;
val table : (Base.int, '_a) Hashtbl.t = <abstr>

You pass in the first-class module, rather than a specific hashable value within the module in question.

We expect to update RWO to use Base instead of Core and Core_kernel, but we’re not quite there yet.

y

2 Likes

A related question while on the topic of the Base vs Core API. Perhaps I’m old-fashioned, but its not uncommon for me to want to use the Base collections as if they had a functorial interface. For example:

module Order_by_start = struct
  type t = Location.t
  include Comparator.Make (struct
      type t = Location.t
      let sexp_of_t _ = Sexp.Atom "_"
      let compare (x: t) (y: t) = compare_position x.loc_start y.loc_start
    end)
end

module Order_by_end = struct
  type t = Location.t
  include Comparator.Make (struct
      type t = Location.t
      let sexp_of_t _ = Sexp.Atom "_"
      let compare (x: t) (y: t) = compare_position x.loc_end y.loc_end
    end)
end

type start_map = (string * Location.t) list Map.M(Order_by_start).t

type end_map = (string * Location.t) list Map.M(Order_by_end).t

type t = start_map * end_map

let empty_start = Map.empty (module Order_by_start)
let empty_end = Map.empty (module Order_by_end)

let empty : t = (empty_start, empty_end)

Is there a slicker way to do this? Note that I want to name the types of the instantiated maps for other uses elsewhere.

I’m bothered by the duplication of the definition of type t 4 times. Normally, like when it’s an alias, this is not too bad, but still bothersome. It is also odd to have to pass each Order_by_ module to both Map.M and to Map.empty.

Cheers, Josh

Well, you can do something like:

module Order_by_start = struct
  include Location 
  include Comparator.Make (struct
      include Location
      let compare (x: t) (y: t) = compare_position x.loc_start y.loc_start
  end)
end

which is a little shorter, and I think should work.

y

Thanks for the quick reply :grinning: This answer is enough to get me up and running with Base Hashtbls for the time being, but in general I’d also like to take this opportunity to get better at reading Ocaml docs. I’m wondering if a seasoned ocaml veteran would have been able to figure this out themselves just by reading the Base.Hashtbl documentation, and if so, how they would go about it.

Concretely, I found the create function by starting from Base.Hashtbl > Creators and expanding the included Hashtbl_intf.Creators. But the function has type ('a key, 'b, unit ‑> ('a, 'b) t) create_options, which appears to be a hidden type, so I got stuck. I noticed that in the included module, create_options was constrained to Hashtbl_intf.create_options_without_hashable, so I clicked on that, but it was equally unhelpful. What am I missing?

Yeah, navigating through the types can be painful. There’s work on making odoc do a better job of peering through the aliases to see the underlying type. Merlin can already do this. In emacs, anyway, if you ask for the type twice, the second time it looks through all the type aliases. So the first time you get this:

('a, 'b, unit -> ('a, 'b) Hashtbl.t)
Hashtbl_intf.create_options_with_first_class_module

and the second time you get this:

(module Hashtbl_intf.Key_plain with type t = 'a) ->
?growth_allowed:bool -> ?size:int -> unit -> ('a0, 'b) Hashtbl.t
1 Like

That’s pretty cool! Never knew merlin could do that. So it seems like it expects a module of type Hashtbl_intf.Key_plain. But then how do I know that this means I’m allowed to pass in String? I looked at the String docs again and I don’t see any references to Key_plain there.

There’s nothing explicit in String that tells you this. You’d have to go and look at Key_plain and see what it requires. For example, if you look at the latest release of Base, you’ll see this signature:

module type Key = sig
  type t [@@deriving compare, sexp_of]

  (** Values returned by [hash] must be non-negative.  An exception will be raised in the
      case that [hash] returns a negative value. *)
  val hash : t -> int
end

This means that any module that has a compare function, a sexp_of function, and a hash function should match this signature, and could be passed in as an argument.

Another possible issue: the hash table in base and core/core_kernel doesn’t seem to be compatible. I came across this issue when the project that was using the base hash table wouldn’t compile when the package was moved to core/core_kernel. I think core/core_kernel requires hashable interface to be implemented whereas in base this is not required.

Additionally, I find the base/core_kernel api not consistent. It seems sometimes you can/must use functor while sometimes you have to use first class modules to use these modules. Maybe if the document was there this wouldn’t be much of an issue. However, since the documention is quite sparse this inconsistency is quite frustrating and time-consuming.

To be clear, the types are compatible, but the APIs for constructing tables and maps do differ. This is something we’re looking to fix, by adjusting the Core/Core_kernel APIs to match Base.

1 Like

Thanks for the update. Looking forward to the next update.