Факторинг присвоений типов продуктов в OCaml - PullRequest
5 голосов
/ 26 февраля 2010

Я, как правило, недоволен написанием такого кода:

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, которая позволила бы мне полностью устранить ее.

Ответы [ 3 ]

2 голосов
/ 27 февраля 2010

Это очень просто: просто используйте замыкание, чтобы выполнить настройку, и напишите функцию для абстрагирования шаблона

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

и т. Д.

Вы можете сделать это еще лучше, если вы используете макропакет вроде Jane Улица полевая Это генерирует первоклассные поля, наряду с автоматически генерируемые сеттеры и геттеры. Это будет означать, что вы не нужно будет каждый раз создавать замыкание вручную.

1 голос
/ 26 февраля 2010

Теоретически самое короткое, с чем вы можете сойти с рук:

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

Конечно, вы будете сорваны OCaml, потому что 1 ° в Objective Caml нет конструкций «указатель на член», поэтому вам придется написать fun a s -> a.a_record_uuid <- s вместо a_record_uuid (как минимум) и 2 ° система типов не полностью поддерживает экзистенциальную количественную оценку, поэтому тип возвращаемой функции не может быть ожидаемым:

exists '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
  ...
0 голосов
/ 27 февраля 2010

Я, как правило, недоволен написанием такого кода

Знак хорошего вкуса, если вы спросите меня: -)


Я знаю обез магии, но я думаю, что лучший способ - разделить шаблон:

  1. Одна функция установки шаблонов для каждого изменяемого поля.Может быть полезно в разных контекстах.

  2. Одна структура данных для сопоставления целочисленных кодов с "что делать с этим полем"

Вы можете реализоватьВаш сканер записей использует таблицу вместо функции.Показательный пример приведен ниже.Разница между gets и gett - настоящий кикер.В дальнейшем

  • sf обозначает «строковое поле»
  • tf обозначает «поле времени»
  • eor обозначает «конец»записи "

Я составил tabulate и lookup в соответствии с моим примером;используйте любую эффективную структуру данных.

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