Preservar invariantes mientras permite la desestructuración

Quiero definir un tipo para que toda la construcción pase por miembros del módulo que puedan conservar invariantes, pero que permitan la desestructuración para la coincidencia de patrones.

Solo estoy aprendiendo OCaml, pero lo siguiente casi funciona para un par int con el invariante de que la izquierda debe ser estrictamente menor que la derecha.

module Range : sig
  type t = private { left:int; right:int }
  exception InvalidRange of (int*int)
  val make : int -> int -> t
end = struct
  type t = { left:int; right:int }
  exception InvalidRange of (int*int)
  let make left right = if left < right
    then { left; right }
    else raise (InvalidRange (left, right))
end

que funciona asi

# let p = Range.make 1 2;;
val p : Range.t = {Range.left = 1; Range.right = 2}
# let q = Range.make 2 1;;
Exception: Range.InvalidRange (2, 1).

y desestructurando las obras de una manera

# let {Range.left=x; Range.right=y} = p;;
val x : int = 1
val y : int = 2

mientras la construcción falla

# let badp = {Range.left = 2; Range.right = 1};;
  let badp = {Range.left = 2; Range.right = 1};;
Error: Cannot create values of the private type Range.t
# open Range;;
# let badp = {left = 2; right=1};;
  let badp = {left = 2; right=1};;
Error: Cannot create values of the private type Range.t

pero lo que realmente me gustaría hacer es tener la conveniencia sintáctica de desestructurar tuplas. Lo siguiente no funciona:

module Range : sig
  type t = private int*int
  exception InvalidRange of (int*int)
  val make : int -> int -> t
end = struct
  type t = int*int
  exception InvalidRange of (int*int)
  let make left right = if left < right
    then (left, right)
    else raise (InvalidRange (left, right))
end

pero luego no puedo desestructurarlo usando un patrón de tupla:

# let r = Range.make 1 2 ;;
val r : Range.t = (1, 2)
# let (a, b) = r;;
  let (a, b) = r;;
Error: This expression has type Range.t
       but an expression was expected of type 'a * 'b

Podría cambiar el tipo a type t = R of (int * int) pero necesito que estos sean lo más livianos posible en términos de memoria. ¿Algunas ideas?

preguntado el 22 de mayo de 12 a las 21:05

5 Respuestas

Como se explica en el manual, necesitas una coerción explícita:

# let (a, b) = (r :> int*int);;
val a : int = 1
val b : int = 2

contestado el 22 de mayo de 12 a las 22:05

Gracias por la referencia al manual. Me va a llevar un tiempo entender cuándo la coerción entre tipos nominales y borrados ocurre automáticamente y cuándo no. ¿La presencia del operador de coerción de tipo significa que la desestructuración siempre es posible, por lo que no es posible ocultar información real (por ejemplo, de mutables o secretos) excepto a través de valores cerrados por funciones? - mike samuel

Hay una diferencia entre 1) tipos transparentes, 2) tipos abstractos, 3a) variante privada o tipos de registro 3b) abreviaturas de tipo privado. Pues 1) la coerción es posible en ambos sentidos y es implícita. Para 2) ninguna coerción es posible en absoluto. Para 3a) la coerción es posible de una manera y parece estar implícita. Para 3b) la coerción también es posible de una forma y parece ser explícita. Es importante tener en cuenta que las coerciones no tienen contenido computacional (se borran en tiempo de ejecución). - Esopo

Gracias. Entonces, el sistema de tipos tiene dos niveles: uno fundamental que tiene una distinción entre tipos concretos y abstractos que afecta el envío de mensajes y uno superficial sujeto a borrado que puede descartar el envío pero no lo afecta. - mike samuel

Una forma sencilla de hacerlo es agregar un to_tuple Funcionar y hacer que el tipo de recorrido sea abstracto.

module Range : sig
  type t
  exception InvalidRange of (int*int)
  val make : int -> int -> t
  val to_tuple : t -> (int * int)
end = struct
  type t = { left:int; right:int }
  exception InvalidRange of (int*int)

  let make left right = if left < right
    then { left; right }
    else raise (InvalidRange (left, right))

  let to_tuple t = t.left, t.right

end

entonces puedes hacer

let (a, b) = to_tuple range

contestado el 23 de mayo de 12 a las 13:05

Tu solución funciona. La diferencia con el que usa tipos privados es que to_tuple tiene un contenido computacional, mientras que la coerción (_ :> _) no lo tiene. Para entender eso, considere un valor de tipo Range.t list e intente convertirlo en una lista de tipo (int*int) list. Con su solución necesita usar List.map to_tuple _ (que copia la lista); con la solución de tipo privado, solo escribes (_ :> (int*int) list) que no hace nada (se borran las coacciones). - Esopo

Tu solución de type t = R of (int * int) será liviano en términos de memoria y es sintácticamente un poco más liviano que la solución de coerción. OCaml optimiza el caso de un tipo de datos de un solo constructor para que no pague por ello. (No tengo una referencia oficial para este reclamo, pero Adam Chlipala (un experto en OCaml) lo menciona aquí: http://adam.chlipala.net/cpdt/html/Subset.html).

contestado el 31 de mayo de 12 a las 21:05

Acabo de hacer esta prueba con Tamaño de objeto (informa tamaños de valores OCaml).

# type fancy = R of int * int;;
type fancy = R of int * int
# Objsize.objsize (R (3, 5));;
- : Objsize.info = {Objsize.data = 2; Objsize.headers = 1; Objsize.depth = 0}
# Objsize.objsize (3,5);;
- : Objsize.info = {Objsize.data = 2; Objsize.headers = 1; Objsize.depth = 0}

Si cree en estos valores (que yo creo), no hay una penalización de tamaño por usar su propio tipo de constructor único en lugar de una tupla.

contestado el 22 de mayo de 12 a las 22:05

No creo que respondas la pregunta. - Tomas

Estaba tratando de responder a la parte en la que dice "Podría cambiar el tipo a tipo t = R de (int * int) pero necesito que estos sean lo más livianos posible en términos de memoria. ¿Alguna idea?" mi reclamo es que R of int * int no es un tipo de peso pesado en cuanto a memoria. - jeffrey scofield

En realidad, la codificación OCaml Runtime es muy simple. Aquí hay una herramienta que puede resultarle útil. https://github.com/bobzhang/caml-inspect

Respondido el 03 de junio de 12 a las 22:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.