我有以下块的数据结构,它可以有一个可以包含多个项目的库存:
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
我怎样才能以更优雅的方式做到这一点?
答案 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.inv
为None
,您的代码就会失败。 (另外,您可能需要b.inv
block.inv
。)
答案 2 :(得分:0)
您能否解释一下库存类型字段的含义?一切都是一种选择味道不好......理想的数据结构只限制了有效状态的表示。如果我能更好地了解in in ...,我可以尝试想象另一个数据结构提议,它只能启用有效的数据状态。这么多可选字段,我感觉不到它