简体   繁体   中英

F#: Error when trying to copy and update record through interface

I am trying to make a function that turns any flat seq<IHierarchy> into a hierarchy. Essentially, anything that has a parentID and a seq of children should be able to be made into a hierarchy. Instead of making Hierarchy a base class with parentID and children properties [we can't as records are sealed classes] I was wondering if it was possible to make it an IHierarchy with two abstract fields that we implement for each class (parentID and children).

I attached the code below including a makeHierarchy function that tries to turn a flat seq<IHierarchy> into a hierarchy structure of IHierarchies. However, when I try using the record copy and update syntax (ie: {node with children = ...}) I am getting an error saying "The type IHierarchy does not contain a field children". I am a bit confused how to get the record {with} syntax to work for this type in the interface. Is it not possible to? Any help would be appreciated as I'm pretty new to 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) )

Do you need an interface for this? You already have the source type defined by the JSON type provider. Why not define a concrete destination type?

In functional programming, the best designs usually separate data from behaviour. Data is data, and functions implement the behaviour. You typically don't need polymorphic objects, although coming from an OOD background, it can be a hard habit to break.

If you need a hierarchy, you can often model it using a generic record type like this:

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

Assuming that you've already defined the SalesComponentJson type using the JSON type provider as above, you can define a function that transforms such JSON data to hierarchies:

// 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 })

From the perspective of the type system, there's no guarantee that any given list of JSON data doesn't hold more than a single entry with no parent ID. Thus, the function returns a list of graphs, or, rather, a forest.

Here's some example data:

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" }""" ]

and here's a usage example from 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 = [];}];}];}]

This forest only has a single tree.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM