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?
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.
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.
Thanks for the quick reply 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
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.