I want to build a module I
with memoization. The type I.t
contains a real complex content c
, and some mutable properties (eg, mutable is_cool
). The module provides outside with functions to calculate and get the properties (eg, is_cool
), which can be costly, that is why mutable properties are used:
(*in i.ml *)
module Content = struct
type t = { i: int; mutable j: int }
let is_cool (x: t) : bool = ...
end
module I : sig
type t
val get_c: t -> Content.t
val is_cool: t -> bool
...
end = struct
type t = {
c : Content.t;
mutable is_cool : bool option;
mutable property_a : int option }
let get_c (x: t) -> Content.t = x.c
let is_cool (x: t) : bool =
match x.is_cool with
| Some r -> r
| None -> (* not yet calculated *)
let r = Content.is_cool x.c in
x.is_cool <- Some r;
r
end
...
A concern that I have, is how to code the module I
and the code outside such that for any value of type I.t
throughout the program execution, its mutable properties are always consistent with its content c
. Being consistent means "the mutable properties should either be None
or be Some v
where v
represents the current property of the content".
For instance, the following code tempting to modify directly a property is well forbidden by the signature of the module I.t
:
(* in main.ml *)
open I
let test (x: I.t) =
x.is_cool <- Some false
However, it seems that it is not easy to forbid this code, which changes the content:
(* in main.ml *)
let test (x: I.t) (i_new: int) =
let c = I.get_c x in
let c_new = { c with Content.i = i_new } in
let y = { x with c = c_new } in
(* now the content and the properties in y are likely to be inconsistent *)
One way to improve this is to add set
in the module I
, and always use set x c_new
at the place of { x with c = c_new }
:
(*in i.ml *)
let set (x: t) (c: Content.t) : t =
{ c = c; is_cool = None; property_a = None }
However, there are still problems, for instance,
1) it is still impossible to forbid people from writing { x with c = c_new }
2) a modification of the mutable components in Content.t
(eg, mutable j: int
) can also make I.t
inconsistent:
(* in main.ml *)
let test (x: I.t) (j_new: int) =
let c = I.get_c x in
c.Content.j <- j_new;
(* now the content and the properties in x are likely to be inconsistent *)
Does anyone know any existing reflexion or solution face to this inconsistency caused by memoization? If we always use set
at the place of "record with", and do not allow mutable components in Content.t
, can we guarantee 100% the consistency for any code scenario?