F #: ошибка при попытке скопировать и обновить запись через интерфейс

#f#

#generics #интерфейс #f# #запись

Вопрос:

Я пытаюсь создать функцию, которая превращает любую квартиру seq<IHierarchy> в иерархию. По сути, все, что имеет родительский идентификатор и последовательность дочерних элементов, должно быть в состоянии быть преобразовано в иерархию. Вместо того, чтобы делать иерархию базовым классом со свойствами ParentID и дочерних элементов [мы не можем, поскольку записи являются закрытыми классами], мне было интересно, можно ли сделать это иерархией с двумя абстрактными полями, которые мы реализуем для каждого класса (ParentID и дочерние элементы).

Я приложил приведенный ниже код, включающий функцию makeHierarchy, которая пытается превратить квартиру seq<IHierarchy> в иерархическую структуру иерархий. Однако, когда я пытаюсь использовать синтаксис копирования и обновления записи (т.Е.: {узел с дочерними элементами = …}) Я получаю сообщение об ошибке «Тип IHierarchy не содержит дочерних полей». Я немного запутался, как заставить синтаксис record {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 amp;amp; sc.parentID.Value = node.ID )
                                      |> Seq.map    (fun sc -> getHierarchy(sc,scs))}
        root |> Option.map (fun r -> getHierarchy(r,hierarchyRecords) )
  

Комментарии:

1. IHeirachy не является записью, поэтому вы не можете использовать синтаксис записи.

2. Это то, что я понял. Любые предложения по шаблону проектирования в F #, чтобы обойти эту проблему? Все варианты, о которых я подумал, кажутся плохими: 1. Не используйте тип записи, используйте базовый класс с полями ParentID и children и наследуйте непосредственно от этого базового класса 2. Сделайте функцию клонирования частью моего интерфейса, сделайте поле children изменяемым, в моей функции клонируйтезапишите, а затем вручную измените дочернее поле. Поскольку оба они являются частью интерфейса iherarchy, это не должно быть проблемой, но это кажется уродливым.

3. Я не вижу элегантной альтернативы.

Ответ №1:

Вам нужен интерфейс для этого? У вас уже есть тип источника, определенный поставщиком типов JSON. Почему бы не определить конкретный тип назначения?

В функциональном программировании лучшие проекты обычно отделяют данные от поведения. Данные — это данные, а функции реализуют поведение. Обычно вам не нужны полиморфные объекты, хотя, исходя из фоновой среды, от этой привычки трудно избавиться.

Если вам нужна иерархия, вы часто можете смоделировать ее, используя общий тип записи, подобный этому:

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

Предполагая, что вы уже определили SalesComponentJson тип с помощью поставщика типов JSON, как указано выше, вы можете определить функцию, которая преобразует такие данные 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 не содержит более одной записи без родительского идентификатора. Таким образом, функция возвращает список графиков или, скорее, лес.

Вот несколько примеров данных:

 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 = [];}];}];}]
  

В этом лесу есть только одно дерево.

Комментарии:

1. Очень полезный ответ. Я думаю, что ключевым моментом, который я не понимал, было использование generics с такими записями. Полностью попробую это.