types - Restringir un tipo polimórfico
functional-programming ocaml (3)
Tengo un tipo de rango definido como:
type ''a range = Full | Range of (''a * ''a)
Sin embargo, me gustaría restringir ''a ser entero o flotar o char, sin otros tipos válidos para'' a.
Range(0,10) (* valid *)
Range(0.0, 10.0) (* valid *)
Range(''a'', ''z'') (* valid *)
Range("string1", "string2") (* other types like this shouldn''t type check *)
Pensé que podría cambiar mis definiciones de tipo a:
type sequential = S_int of int | S_float of float | S_char of char ;;
type range = Full | Range of (sequential * sequential);;
Sin embargo, esto permitiría algo así como:
Range(S_int(0), S_float(10.0));; (* problem: mixes int and float *)
... pero quiero que ambos componentes de Range sean del mismo tipo.
Supongo que otro enfoque sería crear un tipo int_range, un tipo float_range y un tipo char_range pero me pregunto si hay otra forma.
Otro enfoque es declarar el tipo privado y exponer las funciones construyéndolo solo con los tipos que desee, por ejemplo:
module Z : sig
type ''a range = private Full | Range of (''a * ''a)
val int_range : int -> int -> int range
val float_range : float -> float -> float range
val string_range : string -> string -> string range
val full : ''a range
end = struct
type ''a range = Full | Range of (''a * ''a)
let create x y = Range (x,y)
let int_range = create
let float_range = create
let string_range = create
let full = Full
end
# open Z;;
# int_range 2 3;;
- : int Z.range = Range (2, 3)
# Range (''a'',''c'');;
Error: Cannot create values of the private type char Z.range
Tomando una pista de lo que haría Haskell (declare una clase de tipo (Sequential a) => Range a
) podría usar un functor:
module Range (S : sig type t end) = struct
type range = Full | Range of (S.t * S.t)
end
y úsala para proporcionar los módulos requeridos:
module IntRange = Range (struct type t = int end)
module FloatRange = Range (struct type t = float end)
module CharRange = Range (struct type t = char end)
La desventaja es que se pierde la parametricidad en el range
; Lo bueno es que sus funciones paramétricas en el range
s ahora viven dentro del Range
del módulo, como probablemente deberían.
En general, Range
s hará una serie de demandas de s Sequential
para compensar la pérdida de parametricidad. Estos requisitos pueden ser claramente especificados en la firma del parámetro del functor:
module type SEQUENTIAL = sig
type t
val to_string : t -> string
val compare : t -> t -> int
(* ... *)
end
module Range (S : SEQUENTIAL) = struct
type t = Full | Range of (S.t * S.t)
let to_string = function
| Full -> "full"
| Range (lo, hi) -> "(" ^ S.to_string lo ^ "," ^ S.to_string hi ^ ")"
let make lo hi =
if S.compare lo hi > 0 then Range (hi, lo) else Range (lo, hi)
end
Para instanciar el Range
en un tipo específico, ahora necesita proporcionar una estructura que lo parametrice correctamente:
module IntRange = Range (struct
type t = int
let to_string = string_of_int
let compare = Pervasives.compare
end)
Entonces puedes usarlo así:
# IntRange.(to_string (make 4 2)) ;;
- : string = "(2,4)"
(usando la nueva sintaxis para la sobrecarga delimitada). Si necesita ocultar la implementación de Range
s detrás de una firma, es posible que deba volver a exportar el tipo de SEQUENTIAL
s, de forma muy similar a como lo hacen las estructuras de datos en la biblioteca estándar:
module Range (S : SEQUENTIAL) : sig
type elt = S.t
type t = private Full | Range of (elt * elt)
val to_string : t -> string
val make : elt -> elt -> t
end = struct
type elt = S.t
type t = Full | Range of (elt * elt)
let to_string = function
| Full -> "full"
| Range (lo, hi) -> "(" ^ S.to_string lo ^ "," ^ S.to_string hi ^ ")"
let make lo hi =
if S.compare lo hi > 0 then Range (hi, lo) else Range (lo, hi)
end
Esto le proporciona encapsulación y tipos translúcidos que pueden combinarse con patrones pero no construirse. Una alternativa para declarar tipos private
en la firma es usar un tipo de vista o una función de desestructuración.
¡Los módulos de OMG son tan complicados!
type ''a range'' = [`Full | `Range of ''a * ''a]
type range = [
| `Int_range of int range''
| `Float_range of float range''
]
Oh dang, tenemos que agregar otro:
type xrange = [
| range
| `String_range of string range''
]