missions de type de produit en OCaml Factoring
-
23-09-2019 - |
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.
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:
-
Une fonction setter pour chaque champ boilerplate mutable. Peut être utile dans des contextes différents.
-
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