2016-03-21 11 views
4

フラットseq<IHierarchy>を階層に変換する関数を作成しようとしています。基本的には、parentIDとseqの子を持つものはすべて階層化することができます。 HierarchyをparentIDとchildrenプロパティを持つ基本クラスにする代わりに、各クラス(parentIDとchildren)に対して実装する2つの抽象フィールドを持つIHierarchyにすることが可能かどうか疑問に思っていました。 。F#:インターフェイスを介してレコードをコピーして更新しようとするとエラーが発生する

フラットseq<IHierarchy>をIHierarchiesの階層構造に変換しようとするmakeHierarchy関数を含む以下のコードを添付しました。しかし、レコードのコピーを使用して構文を更新しようとすると(つまり{node with children = ...})、 "IHierarchy型にフィールドchildrenが含まれていません"というエラーが表示されます。私は、インターフェイスでこのタイプのために動作するための構文{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)) 
+2

IHeirachyはレコードではないため、レコード構文は使用できません。 –

+0

それは私が考えたものです。この問題を回避するためのF#のデザインパターンに関する提案はありますか? 1.レコードタイプを使用せず、parentIDとchildrenフィールドを持つ基本クラスを使用し、この基本クラスから直接継承します。 2.クローン関数の一部として作成する私のインターフェイスは、子供のフィールドを変更可能にする、私の関数では、レコードをクローンし、手動で子フィールドを変更します。これらの両方がIHierarchyインターフェイスの一部であるため、これは問題ではありませんが、醜いようです。 – gnicholas

+1

私はエレガントな選択肢を見ることができません。 –

答えて

3

インターフェイスが必要ですか?すでに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 = [];}];}];}] 

この森は、単一のツリーを持っています。

+0

非常に有益な答えです。私が理解していなかった重要なことは、ジェネリック医薬品をそのようなレコードで使用していたことだと思います。完全にこれを試してみましょう。 – gnicholas

関連する問題