Le variabili mutabili che possono capitare di avere lo stesso contenuto possono ancora essere distinte perché sono memorizzate in posizioni diverse nella memoria. Possono essere confrontati con l'operatore di uguaglianza fisica (==
). Tuttavia, OCaml non fornisce nulla di meglio dell'uguaglianza, non fornisce una funzione hash non banale o un ordine sui riferimenti, quindi l'unica struttura di dati che è possibile creare per memorizzare riferimenti è un elenco di associazioni di qualche forma, con $ \ Theta (n) $ tempo di accesso per la maggior parte degli usi.
(Si può effettivamente raggiungere il puntatore sottostante se giochi sporco, ma il puntatore può spostarsi sotto i piedi. C'è comunque un modo per utilizzarlo, ma se hai bisogno di chiedere, non dovresti usare it. E non sei abbastanza disperato per quello comunque.)
Sarebbe facile confrontare i riferimenti se due riferimenti distinti avevano un contenuto distinto. Quindi fallo! Aggiungi un identificatore univoco ai tuoi riferimenti. Mantieni un contatore globale, incrementalo di 1 ogni volta che crei un riferimento e memorizza il valore del contatore con i dati. Ora i tuoi riferimenti possono essere indicizzati dal loro valore contatore.
Per una migliore sicurezza del tipo e una migliore efficienza, definire una struttura dati contenente un valore del contatore e un elemento.
let counter = ref 0
type counter = int
type 'a variable = {
key : counter;
mutable data : 'a;
}
let new_var x = incr counter; {key = !counter; data = x}
let hash_var v = Hashtbl.hash v.key
È possibile inserire il codice di cui sopra in un modulo e fare il counter
tipo astratto. Inoltre, è possibile definire un modulo tabella hash utilizzando l'interfaccia funzionale Hashtbl
. Ecco un altro modo per definire variabili e una struttura tabella hash su di loro con una struttura più pulita, più modulare.
module Counter = (struct
type t = int
let counter = ref 0
let next() = incr counter; !counter
let value c = c
end : sig
type t
val next : unit -> t
val value : t -> int
end)
module Variable = struct
type 'a variable = {
mutable data : 'a;
key : Counter.t;
}
let make x = {key = Counter.next(); data = x}
let update v x = v.data <- x
let get v = v.data
let equal v1 v2 = v1 == v2
let hash v = Counter.value v.key
let compare v1 v2 = Counter.value v2.key - Counter.value v1.key
end
module Make = functor(A : sig type t end) -> struct
module M = struct
type t = A.t Variable.variable
include Variable
end
module Hashtbl = Hashtbl.Make(M)
module Set = Set.Make(M)
module Map = Map.Make(M)
end
abbiamo bisogno del modulo intermedio Variable
perché il tipo variable
è parametrico ei funtori struttura dei dati della libreria standard (Hashtbl.Make
, Set.Make
, Map.Make
) sono definiti solo per i costruttori di tipo con nessun argomento. Ecco un'interfaccia che espone sia l'interfaccia polimorfica (con le funzioni associate, ma senza strutture dati) e un functor per creare un numero qualsiasi di istanze monomorfiche, con un tipo di tabella hash associata (e set e mappa).
module Variable : sig
type 'a variable
val make : 'a -> 'a variable
val update : 'a variable -> 'a -> unit
val get : 'a variable -> 'a
val equal : 'a -> 'a -> bool
val hash : 'a variable -> int
val compare : 'a variable -> 'b variable -> int
end
module Make : functor(A : sig type t end) -> sig
module M : sig
type t = A.t variable.variable
val make : A.t -> t
val update : t -> A.t -> unit
val get : t -> A.t
val equal : t -> t -> bool
val hash : t -> int
val compare : t -> t -> int
end
module Hashtbl : Hashtbl.S with type key = M.t
module Set : Set.S with type key = M.t
module Map : Map.S with type key = M.t
end
Si noti che se si prevede che il programma può generare più di 2^30 variabili durante una corsa, un int
non è tagliato. Sono necessari due valori int
per creare un valore 2^a 60 bit o uno Int64.t
.
Si noti che se il programma è multithread, è necessario un blocco attorno al contatore per rendere l'incremento e la ricerca atomici.
Questo non è un problema di Hashtable: se si utilizza "[1; 2]" come chiave, perché si prevede di trovare il valore con il tasto "[5; 1; 2]"? Ciò funzionerebbe solo in una strana lingua chiamata per nome. – lambdapower
@lambdapower, non sta usando [1; 2], sta usando un riferimento, che ha identità. Semanticamente, ha perfettamente senso digitare l'identità. Può essere costoso da implementare. Ma alcune lingue pagano quel costo. –