Question

Je suis généralement satisfait avec le code écrit comme ceci:

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

J'ai minimisait le passe-partout, mais je me demandais s'il y avait une magie OCaml qui me permettrait de les éliminer complètement.

Était-ce utile?

La solution

est mort simple: il suffit d'utiliser une fermeture pour faire le réglage, et d'écrire une fonction abstraite le boilerplate

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)
  ...

et ainsi de suite.

Vous pouvez faire encore mieux si vous utilisez un package macro comme Jane La fieldslib de rue. Cela génère des champs de première classe, ainsi que automatiquement généré et poseurs getters. Cela signifie que vous ne pas avoir à construire la fermeture à chaque fois à la main.

Autres conseils

Le plus court que vous pourriez sortir avec en théorie est:

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

Bien sûr, vous serez déjoué par OCaml parce que 1 ° il n'y a pas « pointeur vers un membre » construit en Objective Caml, vous donc devoir écrire fun a s -> a.a_record_uuid <- s au lieu de a_record_uuid (au moins) et 2 ° le système de type ne supporte pas entièrement la quantification existentielle, de sorte que le type de retour de la fonction ne peut pas être prévu:

  

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

Je suppose que vous pouvez résoudre 1 ° en ayant des fonctions nommées pour définir des valeurs dans un enregistrement, si vous arrivez à le faire assez souvent:

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

Il est un peu peu orthodoxe, je suppose, mais il paie habituellement au loin plus tard parce que je tendance à les utiliser dans des situations les plus fonctionnelles. Vous pouvez créer l'équivalent dans un style impératif, ou vous pouvez accepter la surcharge d'une fonction (il ajoute seulement environ 20 caractères).

ou 2 °, il peut être résolu en cachant le quantificateur existentiel dans une fonction:

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

Cela vous laisser aller jusqu'à:

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

Je ne serais pas surpris si Camlp4 a soutenu une sorte de sucre pour les fonctions d'affectation. Dans le même temps, si vous utilisez des références au lieu de champs mutables, vous pouvez raccourcir ce haut (parce que les références sont les premières valeurs de la classe, les champs ne sont pas):

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

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

Je suis généralement satisfait avec le code d'écriture comme celui-ci

Un signe de bon goût, si vous me demandez: -)


Je ne connais pas la magie, mais je pense que le meilleur chemin est de diviser le boilerplate:

  1. Une fonction setter pour chaque champ boilerplate mutable. Peut être utile dans des contextes différents.

  2. Une structure de données pour cartographier les codes entiers de "quoi faire avec ce champ"

Vous pouvez implémenter votre scanner d'enregistrement à l'aide d'une table au lieu d'une fonction. Un exemple suggestif apparaît ci-dessous. La différence entre gets et gett est un vrai kicker. Dans ce qui suit,

  • sf signifie "champ de chaîne"
  • tf signifie "champ de temps"
  • eor signifie "fin de l'enregistrement"

Je l'ai fait jusqu'à tabulate et lookup en fonction de mon exemple; utiliser des données quelle que soit la structure est efficace.

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top