With a ref containing a single mutable value as shown, it isn't possible to use a Hashtbl to optimize the preservation of sharing. You can't hash the address of a value because GC may change that address.
So in this case a (less performant) scan must be used to preserve sharing. The following example uses a list of pairs, (r1, r2)
, where r1 is the original ref in the list and r2 is the new ref. This updates
list is scanned using List.find_opt
to find r1 refs that occur more than once in the original list, and use the corresponding r2 in the new list.
let copy_ref_list (ls : int ref list) : int ref list =
let rec copy updates = function
| [] -> []
| r :: rs -> (
match List.find_opt (fun (r1, _) -> r == r1) updates with
| Some (_, r2) -> r2 :: copy updates rs
| None ->
let r2 = ref !r in
let updates' = (r, r2) :: updates in
r2 :: copy updates' rs )
in
copy [] ls
Another approach is to define the mutable value as a field in a record along with a unique id field, and use a Hashtbl with a hash on the unique id. A ref would not be used in this case. For example:
type 'a val_with_id = {mutable value: 'a; unique_id: int}
let copy_val_with_id_list (ls : 'a val_with_id list) : 'a val_with_id list = ...
This is a more intrusive solution since it requires maintaining this record and assigning a unique id each time a value is created. It also uses more memory. However, it may provide better copying performance for large lists since a hash lookup is used instead of a list scan per copied element.
For details on creating such a Hashtbl and maintaining a unique id see: https://stackoverflow.com/a/10290155/2607027
Here is a minimal example of using a Hashtbl and val_with_id, using code roughly similar to the code posted in the question.
let next_id =
let id_sequence = ref (-1) in
fun () -> incr id_sequence ; !id_sequence
type 'a val_with_id = {mutable value: 'a; unique_id: int}
let with_id v = {value= v; unique_id= next_id ()}
let copy_val_with_id_list (ls : 'a val_with_id list) : 'a val_with_id list =
let memo = Hashtbl.create 17 in
let copy r =
try Hashtbl.find memo r.unique_id with
| Not_found ->
let r' = with_id r.value in
Hashtbl.add memo r.unique_id r' ;
r'
in
List.map copy ls