如何轻松更改OCaml中嵌套选项的值?

时间:2017-12-29 17:07:31

标签: data-structures ocaml optional matching

我有以下块的数据结构,它可以有一个可以包含多个项目的库存:

type item = Stone | Sand
type stack = {
  item : item;
  size : int
}
type inventory = {
  inA : stack option;
  inB : stack option;
  out : stack option;
  prc : item option
}
type blockType = Container | Solid 
type block = {
  blockType : blockType;
  pos : vec;
  oriented : vec option;
  netID : int option;
  sysID : int option;
  inv : inventory option;
  isMachine : bool
}

理论上,我想写一个非常简单的功能,它只是将一个项目添加到块库存的 out 槽中。

代码

  let addItem block = 
  let oldInv = get_some b.inv in
  if is_some oldInv.out
    then
      let oldOut = get_some oldInv.out in
      let newOut = {oldOut with size = oldOut.size+1} in
      let newInv = {oldInv with out = Some newOut} in
      {block with inv = Some newInv}
    else
      let newInv = {oldInv with out = Some {item=Stone; size=1}} in
      {block with inv = Some newInv}

我甚至使用这些 heler 函数来避免使用多个嵌套匹配块

let is_some v = 
  match v with
  | None -> false
  | Some _ -> true

let get_some v = 
  match v with
  | None   -> raise (Er "no some")
  | Some s -> s

我怎样才能以更优雅的方式做到这一点?

3 个答案:

答案 0 :(得分:2)

为简化代码,您找到了可用于选项的辅助函数。考虑这个辅助函数:

Subaccount.find(params[:id]).maps

这定义了一个在左侧选择一个选项的运算符。如果选项为let (>>=) o f = match o with | None -> None | Some x -> f x ,则此运算符的计算结果为None(不更改输入)。如果选项为None,则操作员将右侧的函数应用于Some x。由于操作员已经可能正在评估选项(无),显然该功能也必须这样做。通过此运算符,您可以轻松使用和链接允许None通过它们不变的函数,或者对选项的内容进行操作。

导致:

x

如果没有广告资源或没有let alter_inv block f = { block with inv = block.inv >>= f } let alter_out block f = alter_inv block (fun inv -> Some { inv with out = inv.out >>= f }) let add_item block = alter_out block (fun out -> Some { out with size = out.size + 1 }) let set_item block item = alter_inv block (fun inv -> match inv.out with | None -> Some { inv with out = Some { item; size = 1 } } | Some _ -> Some inv) 广告位的广告资源,out会保持不变。给定带有广告资源的广告资源以及add_item block广告位中的内容,out会增加点数。

然而,在评论中你说

  

实际上[在None上出现错误]不应该是一个问题,因为函数只会被块调用,而实际情况并非如此。

也就是说,代码的那部分并不是真正处理选项。您的类型不再与现实相匹配,并且您放弃了类型检查器的安全性:您说,代码的那部分不会在运行时引发任何错误,而不是因为编译器证明它不会通过类型系统,但因为你已经彻底检查了逻辑。如果您稍后在该逻辑中引入了错误,则编译器将无法向您发出警告;您唯一的通知将是意外的运行时错误。

答案 1 :(得分:1)

以下是我可能会写的内容:

let addItem block =
    let newstack () = Some { item = Stone; size = 1 } in
    let newinv () =
        Some {
            inA = None; inB = None; out = newstack (); prc = None
        }
    in
    match block.inv with
    | None ->
        { block with inv = newinv () }
    | Some ({ out = None; _ } as inv) ->
        { block with inv = Some { inv with out = newstack () } }
    | Some ({ out = Some stack; _ } as inv) ->
        { block with inv =
            Some { inv with
                out = Some { stack with size = stack.size + 1 }
            }
        }

优雅是主观的。但我认为这一点是使用模式匹配来实现它的好处。您的代码似乎不必要地避免模式匹配。

FWIW,如果block.invNone,您的代码就会失败。 (另外,您可能需要b.inv block.inv。)

答案 2 :(得分:0)

您能否解释一下库存类型字段的含义?一切都是一种选择味道不好......理想的数据结构只限制了有效状态的表示。如果我能更好地了解in in ...,我可以尝试想象另一个数据结构提议,它只能启用有效的数据状态。这么多可选字段,我感觉不到它