2010-02-26 15 views
5

En general estoy satisfecho con la escritura de código como esto:Factoring asignaciones de tipo de producto en OCaml

let load_record_field cursor gets geti gett a = function 
    | 0x01 -> let c, s = gets() in (a.a_record_uuid <- s; `More_record c) 
    | 0x02 -> let c, s = gets() in (a.a_group <- s; `More_record c) 
    | 0x03 -> let c, s = gets() in (a.a_title <- s; `More_record c) 
    | 0x04 -> let c, s = gets() in (a.a_username <- s; `More_record c) 
    | 0x07 -> let c, t = gett() in (a.a_creation_time <- t; `More_record c) 
    . 
    . 
    . 
    | 0xFF -> `End_of_record cursor 

he minimizado el repetitivo, pero me preguntaba si había alguna magia OCaml que me permitiera eliminar completamente eso.

Respuesta

2

Esto es muy simple: sólo tiene que utilizar un cierre de hacer el ajuste, y escribir una función para abstraer el texto modelo

let load_record_field cursor gets geti gett a x = 
    let frob get set = 
    let (c,s) = get() in 
    set s; `More_record c 
    in 
    function 
    | 0x01 -> frob gets (fun s -> a.a_record_uuid <- s) 
    | 0x02 -> frob gets (fun s -> a.a_group <- s) 
    | 0x03 -> frob gett (fun s -> a.a_title <- s) 
    ... 

y así sucesivamente.

Puede hacerlo aún mejor si utiliza un paquete de macro como el fieldlib de Jane . Eso genera campos de primera clase, junto con setters y getters generados automáticamente. Esto significa que usted no tendría que construir el cierre cada vez a mano.

+0

Vergonzosamente simple. – mbac32768

1

El más corto que podría salirse con la teoría es:

frobnicate (function 
| 0x01 -> gets , a_record_uuid 
| 0x02 -> gets , a_group 
    ... 
) 

Por supuesto, usted será frustrado por OCaml porque: 1 ° No hay "puntero a miembro" construye en Objective Caml, por lo que lo haría tener que escribir fun a s -> a.a_record_uuid <- s en lugar de a_record_uuid (por lo menos) y 2 ° el sistema de tipos no es totalmente compatible cuantificación existencial, por lo que el tipo de retorno de la función no puede ser la esperada:

exists 'a. int -> (unit -> record * 'a) * ('a -> record -> unit)

Creo que se puede resolver 1 ° por tener funciones con nombre para establecer valores en un registro, si quieres pasar a hacerlo con la suficiente frecuencia:

type complex = { re : int ; im : int } 
let re r c = { c with re = r } 
let im r c = { c with im = i } 

Es un poco ortodoxo, supongo, pero por lo general vale la pena más adelante porque tiendo a usarlos en la mayoría de las situaciones funcionales. Podría crear el equivalente en estilo imperativo, o podría aceptar la sobrecarga de una función (solo agrega alrededor de 20 caracteres).

como o 2 °, que puede ser resuelto por ocultar el cuantificador existencial en una función:

let t e read write = let c, x = read() in write x e ; `More_record c 

Esto le permitiría bajar a:

let t = t a in 
match 
    | 0x01 -> t gets a_record_uuid 
    | 0x02 -> t gets a_title 
    ... 

que no se sorprendería si CamlP4 admite algún tipo de azúcar para las funciones de asignación. Por el momento, si utiliza referencias en lugar de campos variables, se puede acortar esto (porque las referencias son valores de primera clase, los campos no son):

let t read reference = let c, x = read() in reference := x ; `More_record c 

match 
    | 0x01 -> t gets a.a_record_uuid 
    ... 
+0

Tenga en cuenta que, precisamente porque las referencias son valores de primera clase, el enfoque "referencias en lugar de campos mutables" cambia por completo el significado de la sintaxis '{existing_record with a_record_uuid = ...}'. Solo por esa razón, preferiría usar funciones setter. –

+0

@Pascal: de hecho. Por otro lado, si tienes campos mutables, esa sintaxis debería usarse con mucho cuidado de todos modos. –

+0

Esto parece una respuesta bastante compleja a una pregunta simple. Los tipos existenciales no juegan un papel serio en esto en la práctica. Lo único que falta en OCaml son los campos de registro de primera clase, aunque se pueden agregar fácilmente usando las macros de camlp4 existentes. En cualquier caso, esta es una pequeña conveniencia sintáctica. – zrr

0

En general estoy satisfecho con la escritura de código como este

Una muestra del buen gusto, si me preguntas :-)


no conozco ningún magia, pero creo que el mejor camino es dividir el texto modelo:

  1. Una función de replanteo repetitivo para cada campo mutable. Puede ser útil en diferentes contextos.

  2. Una estructura de datos para asignar códigos enteros a "qué hacer con este campo"

puede implementar su escáner de registro utilizando una tabla en lugar de una función. Un ejemplo sugerente aparece a continuación. La diferencia entre gets y gett es un verdadero truco aquí. En lo que sigue,

  • sf significa "campo de cadena"
  • tf significa "campo de tiempo"
  • eor significa "fin de la comunicación"

me he hecho a la tabulate y lookup para mi ejemplo; use la estructura de datos que sea eficiente.

let sf set a c =  let c, s = gets() in (set a s; `More_record c) 
let tf set a c =  let c, s = gett() in (set a t; `More_record c) 
let eor a c =  `End_of_record c 

let fields = tabulate 
    [ 0x01, sf a_record_uuid 
    ; 0x02, sf a_group 
    ; ... 
    ; 0x07, tf a_creation_time 
    ; ... 
    ] 

let load_record_field cursor gets geti gett a code = lookup fields code cursor a