【问题标题】:F#: Error when trying to copy and update record through interfaceF#:尝试通过界面复制和更新记录时出错
【发布时间】:2016-03-21 04:32:32
【问题描述】:

我正在尝试创建一个将任何平面 seq<IHierarchy> 转换为层次结构的函数。本质上,任何具有 parentID 和一系列子项的东西都应该能够构成层次结构。而不是让 Hierarchy 成为具有 parentID 和 children 属性的基类 [我们不能因为记录是密封类] 我想知道是否有可能将其作为具有我们为每个类(parentID 和孩子)实现的两个抽象字段的 IHierarchy .

我附上了下面的代码,其中包括一个 makeHierarchy 函数,该函数试图将平面 seq<IHierarchy> 转换为 IHierarchies 的层次结构。但是,当我尝试使用记录复制和更新语法(即:{node with children = ...})时,我收到一条错误消息,提示“IHierarchy 类型不包含子字段”。我有点困惑如何让记录 {with} 语法在界面中适用于这种类型。不可能吗?任何帮助将不胜感激,因为我对 F# 还很陌生。

module Hierarchy = 
    type IHierarchy =
        abstract member parentID: Option<int> 
        abstract member children: seq<IHierarchy>

module SalesComponents = 
    open Hierarchy
    type SalesComponentJson = JsonProvider<""" [{ "ID":1, "parentID":0, "name":"All Media" }, { "ID":1, "parentID":null, "name":"All Media" }]  """, SampleIsList=true>
    type SalesComponent  = {
                            ID: int; 
                            parentID: Option<int>; 
                            children: seq<SalesComponent>; 
                            name: string
                           }
                           interface IHierarchy with
                            member x.parentID = x.parentID 
                            member x.children = x.children |> Seq.map (fun c -> c :> IHierarchy)

open Hierarchy
open SalesComponents
let main argv =   
    let makeHierarchy hierarchyRecords:seq<IHierarchy> = 
        let root = hierarchyRecords |> Seq.tryFind (fun sc -> sc.parentID.IsNone)
        let rec getHierarchy (node: IHierarchy, scs: seq<IHierarchy>) = 
            {node with children = scs |> Seq.filter (fun sc -> sc.parentID.IsSome && sc.parentID.Value = node.ID )
                                      |> Seq.map    (fun sc -> getHierarchy(sc,scs))}
        root |> Option.map (fun r -> getHierarchy(r,hierarchyRecords) )

【问题讨论】:

  • IHeirachy 不是记录,所以不能使用记录语法。
  • 我就是这么想的。关于 F# 中的设计模式以解决此问题的任何建议?我想到的选项似乎都很糟糕: 1. 不要使用记录类型,使用具有 parentID 和子字段的基类,并直接从该基类继承 2. 将克隆函数作为我接口的一部分,使子字段可变,在我的函数中克隆记录,然后手动改变子字段。由于这两个都是 IHierarchy 接口的一部分,所以这应该不是问题,但看起来很难看。
  • 我看不到一个优雅的选择。

标签: generics interface f# record


【解决方案1】:

你需要一个接口吗?您已经拥有 JSON 类型提供程序定义的源类型。为什么不定义一个具体的目的地类型?

在函数式编程中,最好的设计通常将数据与行为分开。数据就是数据,函数实现行为。您通常不需要多态对象,尽管来自 OOD 背景,但它可能是一个很难改掉的习惯。

如果您需要层次结构,通常可以使用这样的通用记录类型对其进行建模:

type Graph<'a> = { Node : 'a; Children : Graph<'a> list }

假设您已经使用上面的 JSON 类型提供程序定义了 SalesComponentJson 类型,您可以定义一个将此类 JSON 数据转换为层次结构的函数:

// FSharp.Data.JsonProvider<...>.Root list -> Graph<string> list
let createHierarchies (xs : SalesComponentJson.Root list) =
    let rec findChildren parentId =
        xs
        |> List.filter (fun x -> x.ParentId = Some parentId)
        |> List.map (fun x -> { Node = x.Name; Children = findChildren x.Id })

    xs
    |> List.filter (fun x -> x.ParentId.IsNone)
    |> List.map (fun root -> { Node = root.Name; Children = findChildren root.Id })

从类型系统的角度来看,不能保证任何给定的 JSON 数据列表不包含多个没有父 ID 的条目。因此,该函数返回一个图表列表,或者更确切地说,是一个森林。

以下是一些示例数据:

let salesComponents = [
    SalesComponentJson.Parse """{ "ID":0, "name":"All Media" }"""
    SalesComponentJson.Parse """{ "ID":1, "parentID":0, "name":"Foo" }"""
    SalesComponentJson.Parse """{ "ID":2, "parentID":1, "name":"Bar" }"""
    SalesComponentJson.Parse """{ "ID":3, "parentID":1, "name":"Baz" }"""
    SalesComponentJson.Parse """{ "ID":4, "parentID":0, "name":"Qux" }"""
    SalesComponentJson.Parse """{ "ID":5, "parentID":4, "name":"Corge" }""" ]

这是来自 FSI 的一个使用示例:

> createHierarchies salesComponents;;
val it : Graph<string> list =
  [{Node = "All Media";
    Children =
     [{Node = "Foo";
       Children = [{Node = "Bar";
                    Children = [];}; {Node = "Baz";
                                      Children = [];}];};
      {Node = "Qux";
       Children = [{Node = "Corge";
                    Children = [];}];}];}]

这片森林只有一棵树。

【讨论】:

  • 非常有帮助的答案。我认为我不理解的关键是使用具有此类记录的泛型。完全会试试这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-24
  • 1970-01-01
  • 1970-01-01
  • 2022-09-27
  • 2019-05-16
  • 1970-01-01
相关资源
最近更新 更多