2010-02-26 5 views
5

Я вообще неудовлетворен с написанием кода, как это:Факторинговые задания типа продукта в 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 

Я минимизировал шаблонного, но мне было интересно, есть ли OCaml магия, которая позволила бы мне полностью устранить Это.

ответ

2

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

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 Поля для полей Street. Это генерирует поля первого класса вместе с автоматически создаваемыми сеттерами и геттерами. Это означало бы, что вам не нужно было бы строить замыкание каждый раз вручную.

+0

Смущающе простой. – mbac32768

1

Кратчайший вы могли бы уйти с в теории:

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

Обратите внимание, что именно потому, что ссылки являются первоклассными значениями, подход «ссылки вместо изменчивых полей» полностью изменяет значение синтаксиса '{existing_record with a_record_uuid = ...}'. Именно по этой причине я предпочел бы использовать функции setter. –

+0

@ Паскаль: действительно. С другой стороны, если у вас есть изменяемые поля, этот синтаксис следует использовать очень осторожно. –

+0

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

0

Я вообще неудовлетворен с написанием кода, как это

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


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

  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