Simply put, I am trying to make generic functors or functions to create a string form for Hashtbl
and Map.S
module types.
I was able to implement such functions on an individual basis, but there is still much redundant code.
Here is what I have as of now:
let string_of_list (string_of_el: 'a -> string) (lst: 'a list): string =
List.map string_of_el lst |> String.concat "; " |> Printf.sprintf "[%s]"
let map_ds_fold_func_generator (string_of_key: 'a -> string) (string_of_val: 'b -> string): 'a -> 'b -> string list -> string list =
let map_fold_func (key: 'a) (value: 'b) (accum: string list): string list =
(Printf.sprintf "(%s, %s)" (string_of_key key) (string_of_val value))::accum
in map_fold_func
let string_of_hashtbl (string_of_key: 'a -> string) (string_of_val: 'b -> string) (tbl: ('a, 'b) Hashtbl.t): string =
string_of_list Fun.id (Hashtbl.fold (map_ds_fold_func_generator string_of_key string_of_val) tbl [])
module StringOfMap (M: Map.S) = struct
let string_of_map string_of_key string_of_val map =
string_of_list Fun.id (M.fold (map_ds_fold_func_generator string_of_key string_of_val) map [])
end
Function explanations:
string_of_list
is pretty self explanatory. It takes in a function that converts the elements of a list
to a string
and a list
and returns a string
representation of the list
.
map_ds_fold_func_generator
's purpose is to abstract the function used to fold the Hashtbl
or Map.S
structure. string_of_key
and string_of_val
are the functions used to convert keys and values, respectively, in key-value pairs of the mapping to string
s. It then returns the function that can be used to convert the key-value pair to a string and add it to the accumulating list for the fold function.
string_of_hashtbl
is used to convert a Hashtbl.t
to a string
. string_of_key
and string_of_val
have the same meaning as above.
string_of_map
in the StringOfMap
module is used to convert a Map.S.t
to a string
. string_of_key
and string_of_val
have the same meaning as above.
Both string_of_hashtbl
and string_of_map
have the same function layout:
let string_of_map_ds string_of_key string_of_val ds =
string_of_list Fun.id (DS.fold (map_ds_fold_func_generator string_of_key string_of_val) ds [])
However, mainly because the definitions for t
are different, I am struggling to find a way to decrease the redundancy in the very similar function layouts.
The following uses the Seq
module to do it. Basically, all you need is a function that turns a sequence into a list string, and then call that with the appropriate data structure to sequence conversion (Itself passed as an argument to a higher level function):
let string_of_list (string_of_el: 'a -> string) (lst: 'a list): string =
List.map string_of_el lst |> String.concat "; " |> Printf.sprintf "[%s]"
let pair_to_string (string_of_fst: 'a -> string)
(string_of_snd: 'b -> string) ((fst:'a), (snd:'b)): string =
let s1 = string_of_fst fst
and s2 = string_of_snd snd in
Printf.sprintf "(%s, %s)" s1 s2
let string_of_seq (string_of_fst: 'a -> string)
(string_of_snd: 'b -> string) (s: ('a * 'b) Seq.t): string =
List.of_seq s |> string_of_list (pair_to_string string_of_fst string_of_snd)
let string_of_ds (string_of_key: 'a -> string)
(string_of_val: 'b -> string)
(to_seq: 'c -> ('a * 'b) Seq.t) (ds:'c): string =
to_seq ds |> string_of_seq string_of_key string_of_val
let string_of_hashtbl (string_of_key: 'a -> string)
(string_of_val: 'b -> string) (tbl: ('a, 'b) Hashtbl.t): string =
string_of_ds string_of_key string_of_val Hashtbl.to_seq tbl
module StringOfMap (M: Map.S) = struct
type key = M.key
type 'a t = 'a M.t
let string_of_map (string_of_key: key -> string)
(string_of_val: 'a -> string) (map: 'a t): string =
string_of_ds string_of_key string_of_val M.to_seq map
end
module StringMap = Map.Make(String)
let _ =
let module PrintableMap = StringOfMap(StringMap) in
let example = [ ("dog", 1); ("cat", 2); ("chicken", 3); ("rat", 4);
("goat", 5)] in
let htable = List.to_seq example |> Hashtbl.of_seq
and smap = List.to_seq example |> StringMap.of_seq in
print_endline (string_of_hashtbl Fun.id Int.to_string htable);
print_endline (PrintableMap.string_of_map Fun.id Int.to_string smap)
Alternatively, you can use something that looks more like your version by lifting the almost-duplicate code out into its own function that takes the appropriate module's fold routine as an argument, much like the above does with the to_seq
functions:
let string_of_list (string_of_el: 'a -> string) (lst: 'a list): string =
List.map string_of_el lst |> String.concat "; " |> Printf.sprintf "[%s]"
let map_ds_fold_func_generator (string_of_key: 'a -> string)
(string_of_val: 'b -> string): 'a -> 'b -> string list -> string list =
fun key value accum -> (Printf.sprintf "(%s, %s)" (string_of_key key)
(string_of_val value))::accum
(* This is why I prefer type inference to explicit typing *)
let string_of_ds (string_of_key: 'a -> string)
(string_of_val: 'b -> string)
(fold_fn: ('a -> 'b -> string list -> string list)
-> 'c -> string list -> string list)
(ds: 'c): string =
string_of_list Fun.id
(fold_fn (map_ds_fold_func_generator string_of_key string_of_val) ds [])
let string_of_hashtbl (string_of_key: 'a -> string)
(string_of_val: 'b -> string) (tbl: ('a, 'b) Hashtbl.t): string =
string_of_ds string_of_key string_of_val Hashtbl.fold tbl
module StringOfMap (M: Map.S) = struct
type key = M.key
type 'a t = 'a M.t
let string_of_map (string_of_key: key -> string)
(string_of_val: 'a -> string) (map: 'a t): string =
string_of_ds string_of_key string_of_val M.fold map
end
module StringMap = Map.Make(String)
let _ =
let module PrintableMap = StringOfMap(StringMap) in
let example = [ ("dog", 1); ("cat", 2); ("chicken", 3); ("rat", 4);
("goat", 5)] in
let htable = List.to_seq example |> Hashtbl.of_seq
and smap = List.to_seq example |> StringMap.of_seq in
print_endline (string_of_hashtbl Fun.id Int.to_string htable);
print_endline (PrintableMap.string_of_map Fun.id Int.to_string smap)