An implementation of a bimap, a map that is bi-directional and which any client code can use to look up keys if given values as well as traditional lookup of values given keys. Implemented using functors as a class and a module, with support for multi-maps as well as well as single-valued maps. Master branch uses Core. A no-core branch is a work-in-progress and needs re-writing. OUnit testing also implemented.
With a moderate amount of non-trivial logic, this seems to be a prime for property-based testing and in particular Crowbar fuzzing. Have you tried to use crowbar?
There seems to be a fair amount of almost-duplication of logic between your module
version (which implements an immutable interface) and your class
version (which implements an imperative interface). Would you be able to refactor the code to eliminate the duplication? One approach would be to call the module
implementation from your class
module (which becomes simply an adapter); you could do this by hand (adding !
and :=
in the right places), but it could even be possible to write generaic adapter combinators, along the lines of
let find = Pure.find |> (id @-> source @!-> id)
let change = Pure.change |> (id @-> id @-> source @!-> sink)
let iter = Pure.iter |> (id @-> source @!-> id)
in the following proof-of-concept
module Pure : sig
type key = bool
type 'a data
val create : 'a * 'a -> 'a data
val find : key -> 'a data -> 'a
val change : key -> 'a -> 'a data -> 'a data
val iter : ('a -> unit) -> 'a data -> unit
end = struct
type key = bool
type 'a data = 'a * 'a
let create (v1, v2) = (v1, v2)
let find b (v1, v2) =
if b then v1 else v2
let change b v (v1, v2) =
if b then (v, v2) else (v1, v)
let iter f (v1, v2) =
f v1;
f v2;
()
end
let test (type a) (v1, v2) =
let module Impure : sig
val find : Pure.key -> a
val change : Pure.key -> a -> unit
val iter : (a -> unit) -> unit
end = struct
let state : a Pure.data ref = ref (Pure.create (v1, v2))
let id x = x
let sink result = state := result
let source () = !state
let (@->) c1 c2 f = fun x -> c2 (f (c1 x))
let (@!->) c1 c2 f = c2 (f (c1 ()))
let find = Pure.find |> (id @-> source @!-> id)
let change = Pure.change |> (id @-> id @-> source @!-> sink)
let iter = Pure.iter |> (id @-> source @!-> id)
end in
()
I just took a look at Crowbar…had not heard of it. But at first glance it seems to be the way to go. I will incorporate it as soon as I get the chance.
Thank you for the excellent suggestions to reduce code duplication. I should have thought of that. Code duplication is the source of all evil (in coding). I like your first suggestion to make the class module an adapter for the other implementation if only because I’m not familiar with generic adapter combinators…yet.