在OCaml中考虑产品类型分配

时间:2010-02-26 14:34:30

标签: types ocaml product boilerplate

我对编写这样的代码通常不满意:

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 个答案:

答案 0 :(得分: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这样的宏包,你可以做得更好 街头的fieldlib。这会产生一流的领域 自动生成的setter和getter。这意味着你 不必每次都手工构建闭包。

答案 1 :(得分: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个字符)。

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

答案 2 :(得分:0)

  

我对编写像这样的代码一般不满意

如果你问我,这是一个好品味的标志: - )


我知道没有魔法,但我认为最好的方法是拆分样板:

  1. 每个可变字段的一个样板设定函数。可能在不同的情况下有用。

  2. 将整数代码映射到“如何处理此字段”的一种数据结构

  3. 您可以使用表格而不是函数来实现记录扫描程序。下面是一个暗示性的例子。 getsgett之间的差异是一个真正的踢球者。 接下来,

    • sf代表“字符串字段”
    • tf代表“时间字段”
    • eor代表“记录结束”

    我已经编造tabulatelookup以适应我的榜样;使用任何有效的数据结构。

    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