Я обычно не удовлетворен с написанием кода как это:
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
Я минимизировал шаблон, но я задавался вопросом, было ли волшебство OCaml, которое позволило бы мне полностью устранить его.
Это предельно просто: просто используйте замыкание для настройки и напишите функцию для абстрагирования шаблона
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)
...
и так далее.
Вы можете сделать это еще лучше, если воспользуетесь пакетом макросов, таким как Fieldslib Джейн Стрит. Это генерирует первоклассные поля вместе с автоматически сгенерированными сеттерами и получателями . Это означало бы, что вам не нужно каждый раз конструировать замыкание вручную.
Самое короткое, что вы могли бы избежать в теории, это:
frobnicate (function
| 0x01 -> gets , a_record_uuid
| 0x02 -> gets , a_group
...
)
Конечно, OCaml помешает вам, потому что на 1 ° нет "указателя на member "конструкции в Objective Caml, поэтому вам придется написать fun как -> a.a_record_uuid <- s
вместо a_record_uuid
(по крайней мере) и 2 ° систему типов не полностью поддерживает количественную оценку существования, поэтому возвращаемый тип функции не может быть ожидаемым:
существует 'a. int -> (unit -> record * 'a) * (' a -> record -> unit)
Я думаю, вы могли бы решить 1 °, указав функции для установки значений в записи, если вы это сделаете достаточно часто:
type complex = { re : int ; im : int }
let re r c = { c with re = r }
let im r c = { c with im = i }
Я думаю, это немного неортодоксально, но обычно окупается позже, потому что я склонен использовать их в большинстве функциональных ситуаций. Вы можете создать эквивалент в императивном стиле или согласиться с накладными расходами функции (она добавляет только около 20 символов).
Как или 2 °, это может быть решено путем сокрытия экзистенциального квантификатора в функции:
let t e read write = let c, x = read () in write x e ; `More_record c
Это позволит вам перейти к:
let t = t a in
match
| 0x01 -> t gets a_record_uuid
| 0x02 -> t gets a_title
...
Я не удивлюсь, если CamlP4 поддерживает какой-то сахар для присваивания функции. Между тем, если вы используете ссылки вместо изменяемых полей, вы можете сократить это (поскольку ссылки являются значениями первого класса, а поля - нет):
let t read reference = let c, x = read () in reference := x ; `More_record c
match
| 0x01 -> t gets a.a_record_uuid
...
Я обычно не удовлетворен написанием такого кода
A признак хорошего вкуса, если вы спросите меня: -)
Я не знаю никакой магии, но я думаю, что лучший способ - разделить шаблон:
Одна функция-установщик шаблонов для каждого изменяемого поля. Может быть полезно в разных контекстах.
Одна структура данных для отображения целочисленных кодов на «что делать с этим полем»
Вы можете реализовать свой сканер записей, используя таблицу вместо функции. Пример, который наводит на размышления, представлен ниже.
Разница между gets
и gett
здесь очень важна.
Далее
sf
означает «строковое поле» tf
означает «поле времени» eor
означает «конец записи» Я составил таблицу
и поиск
для моего примера; используйте любую эффективную структуру данных.
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