如何使用单根建模简单层次结构

时间:2018-06-13 14:00:37

标签: f#

我想在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)

是否有更惯用的方式?

2 个答案:

答案 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}}类型以及附带的模块,该模块封装了有效标识符的所有业务规则
  • intRootNode的特定类型,允许它们只包含该类型节点的必填字段
  • 单个ChildNode类型,允许我们将NodeRootNode表示为相同的类型,而不要求它们具有相同的字段,但仍提供对基础字段的直接访问权限允许我们轻松区分ChildNodeRootNode s

然后,我会创建一个用于创建ChildNode的模块,如:

Node

答案 1 :(得分:1)

生成树的纯功能惯用方式是使用不引用父级的Tree <'a>。...树...是树,“父级”属性是从现有节点的子节点。

然后,您可以使用拉链在树上从父级到子级以及子级到父级之间进行导航……就像项目列表中的“光标”一样。

请参阅 http://tomasp.net/blog/tree-zipper-query.aspx/

如果要创建自己的树,则可以使用不同的根类型和非根类型,其中只允许将非根添加到现有节点。然后,您可能还必须自己滚动拉链,所以成本很高。