我想在F#中建模一个层次结构,其中每个节点必须有一个父节点,显然是根节点,它没有父节点。我天真的解决方案
type Node = {
Id: int // meta data for node
Parent: Node option
}
let root = { Id = 1; Parent = None}
let child1 = { Id = 2; Parent = Some(root)}
let child2 = { Id = 3; Parent = Some(child1)}
但是我进入F#已经通过@swlaschin而且他在创建描述性域名时感到震惊。因此,Parent
是一种选择,对我来说感觉很臭,99%的情况下,这是必需的。我尽力而为:
type Node =
| Node of NodeMeta * Node
| Root of NodeMeta
and NodeMeta = {
Id: int
}
let root = Root({Id = 1})
let child1 = Node({Id = 2}, root)
let child2 = Node({Id = 3}, child1)
是否有更惯用的方式?
答案 0 :(得分:7)
如果我在域驱动设计中为我自己的模型构建这个,我可能会按如下方式定义节点:
[<Struct>] type NodeId = private NodeId of int
module NodeId =
let create id =
// Replace with the proper validation rules for a Node Id
if id < 0
then Error "NodeId must be non-negative" // I would actually use a DU with each error case
else Ok <| NodeId id
let value (NodeId id) = id
type [<Struct>] RootNode = {Id: NodeId}
type [<Struct>] ChildNode = {Parent: Node; Id: NodeId}
and Node =
| Root of RootNode
| Node of ChildNode
member node.Id =
match node with
| Root r -> r.Id
| Node n -> n.Id
member node.Parent =
match node with
| Root _ -> None
| Node n -> Some n.Parent
member node.IsRootNode =
match node with
| Root _ -> true
| Node _ -> false
member node.IsChildNode =
not node.IsRootNode
这给了我们以下内容:
NodeId
的{{1}}类型以及附带的模块,该模块封装了有效标识符的所有业务规则int
和RootNode
的特定类型,允许它们只包含该类型节点的必填字段ChildNode
类型,允许我们将Node
和RootNode
表示为相同的类型,而不要求它们具有相同的字段,但仍提供对基础字段的直接访问权限允许我们轻松区分ChildNode
和RootNode
s 然后,我会创建一个用于创建ChildNode
的模块,如:
Node
答案 1 :(得分:1)
生成树的纯功能惯用方式是使用不引用父级的Tree <'a>。...树...是树,“父级”属性是从现有节点的子节点。
然后,您可以使用拉链在树上从父级到子级以及子级到父级之间进行导航……就像项目列表中的“光标”一样。
请参阅 http://tomasp.net/blog/tree-zipper-query.aspx/
如果要创建自己的树,则可以使用不同的根类型和非根类型,其中只允许将非根添加到现有节点。然后,您可能还必须自己滚动拉链,所以成本很高。