Erzwingen von F # -Typ-Inferenzen auf Generika und Schnittstellen, um lose zu bleiben
Wir werden hier haarig. Ich habe eine Reihe von baumsynchronisierendem Code auf konkreten Darstellungen von Daten getestet und muss ihn jetzt abstrahieren, damit er mit jeder Quelle und jedem Ziel ausgeführt werden kann, die die richtigen Methoden unterstützen. [In der Praxis sind dies Quellen wie Documentum, SQL-Hierarchien und Dateisysteme. mit Zielen wie Solr und einem benutzerdefinierten SQL-Querverweisspeicher.]
Der knifflige Teil ist, dass ich einen Baum vom Typ rekursiveT
und Synchronisieren in einen Baum vom TypU
Bei bestimmten Dateien muss ich eine "Untersynchronisation" eines zweiten Typs durchführenV
zu diesem TypU
am aktuellen Knoten. (V
repräsentiert eine hierarchische StrukturInnerhalb eine Datei ...) Und die Typ-Inferenz-Engine in F # treibt mich in Kreisen herum, sobald ich versuche, die Untersynchronisation hinzuzufügenV
.
Ich vertrete dies in einemTreeComparison<'a,'b>
, so ergibt das obige Zeug eineTreeComparison<T,U>
und ein Teilvergleich vonTreeComparison<V,U>
.
Das Problem ist, sobald ich einen Beton liefereTreeComparison<V,'b>
in einer der Klassenmethoden ist dieV
Typ propagiert durch alle Inferenz, wenn ich möchte, dass der erste Typparameter generisch bleibt (when 'a :> ITree
). Vielleicht kann ich etwas über das tippenTreeComparison<V,'b>
Wert? Oder, wahrscheinlicher, die Schlussfolgerung sagt mir tatsächlich, dass etwas in der Art und Weise, wie ich über dieses Problem nachdenke, von Natur aus kaputt ist.
Das war wirklich schwierig zu komprimieren, aber ich möchte Ihnen Arbeitscode geben, mit dem Sie in ein Skript einfügen und experimentieren können. Am Anfang stehen also eine Menge Typen ... Kernelemente befinden sich direkt am Ende, wenn Sie überspringen möchten. Der größte Teil des tatsächlichen Vergleichs und der Rekursion zwischen den Typen über ITree wurde gehackt, da das Inferenzproblem, gegen das ich mich stelle, nicht mehr zu sehen ist.
open System
type TreeState<'a,'b> = //'
| TreeNew of 'a
| TreeDeleted of 'b
| TreeBoth of 'a * 'b
type TreeNodeType = TreeFolder | TreeFile | TreeSection
type ITree =
abstract NodeType: TreeNodeType
abstract Path: string
with get, set
type ITreeProvider<'a when 'a :> ITree> = //'
abstract Children : 'a -> 'a seq
abstract StateForPath : string -> 'a
type ITreeWriterProvider<'a when 'a :> ITree> = //'
inherit ITreeProvider<'a> //'
abstract Create: ITree -> 'a //'
// In the real implementation, this supports:
// abstract AddChild : 'a -> unit
// abstract ModifyChild : 'a -> unit
// abstract DeleteChild : 'a -> unit
// abstract Commit : unit -> unit
/// Comparison varies on two types and takes a provider for the first and a writer provider for the second.
/// Then it synchronizes them. The sync code is added later because some of it is dependent on the concrete types.
type TreeComparison<'a,'b when 'a :> ITree and 'b :> ITree> =
{
State: TreeState<'a,'b> //'
ATree: ITreeProvider<'a> //'
BTree: ITreeWriterProvider<'b> //'
}
static member Create(
atree: ITreeProvider<'a>,
apath: string,
btree: ITreeWriterProvider<'b>,
bpath: string) =
{
State = TreeBoth (atree.StateForPath apath, btree.StateForPath bpath)
ATree = atree
BTree = btree
}
member tree.CreateSubtree<'c when 'c :> ITree>
(atree: ITreeProvider<'c>, apath: string, bpath: string)
: TreeComparison<'c,'b> = //'
TreeComparison.Create(atree, apath, tree.BTree, bpath)
/// Some hyper-simplified state types: imagine each is for a different kind of heirarchal database structure or filesystem
type T( data, path: string ) = class
let mutable path = path
let rand = (new Random()).NextDouble
member x.Data = data
// In the real implementations, these would fetch the child nodes for this state instance
member x.Children() = Seq.empty<T>
interface ITree with
member tree.NodeType =
if rand() > 0.5 then TreeFolder
else TreeFile
member tree.Path
with get() = path
and set v = path <- v
end
type U(data, path: string) = class
inherit T(data, path)
member x.Children() = Seq.empty<U>
end
type V(data, path: string) = class
inherit T(data, path)
member x.Children() = Seq.empty<V>
interface ITree with
member tree.NodeType = TreeSection
end
// Now some classes to spin up and query for those state types [gross simplification makes these look pretty stupid]
type TProvider() = class
interface ITreeProvider<T> with
member this.Children x = x.Children()
member this.StateForPath path =
new T("documentum", path)
end
type UProvider() = class
interface ITreeProvider<U> with
member this.Children x = x.Children()
member this.StateForPath path =
new U("solr", path)
interface ITreeWriterProvider<U> with
member this.Create t =
new U("whee", t.Path)
end
type VProvider(startTree: ITree, data: string) = class
interface ITreeProvider<V> with
member this.Children x = x.Children()
member this.StateForPath path =
new V(data, path)
end
type TreeComparison<'a,'b when 'a :> ITree and 'b :> ITree> with
member x.UpdateState (a:'a option) (b:'b option) =
{ x with State = match a, b with
| None, None -> failwith "No state found in either A and B"
| Some a, None -> TreeNew a
| None, Some b -> TreeDeleted b
| Some a, Some b -> TreeBoth(a,b) }
member x.ACurrent = match x.State with TreeNew a | TreeBoth (a,_) -> Some a | _ -> None
member x.BCurrent = match x.State with TreeDeleted b | TreeBoth (_,b) -> Some b | _ -> None
member x.CreateBFromA =
match x.ACurrent with
| Some a -> x.BTree.Create a
| _ -> failwith "Cannot create B from null A node"
member x.Compare() =
// Actual implementation does a bunch of mumbo-jumbo to compare with a custom IComparable wrapper
//if not (x.ACurrent.Value = x.BCurrent.Value) then
x.SyncStep()
// And then some stuff to move the right way in the tree
member internal tree.UpdateRenditions (source: ITree) (target: ITree) =
let vp = new VProvider(source, source.Path) :> ITreeProvider<V>
let docTree = tree.CreateSubtree(vp, source.Path, target.Path)
docTree.Compare()
member internal tree.UpdateITree (source: ITree) (target: ITree) =
if not (source.NodeType = target.NodeType) then failwith "Nodes are incompatible types"
if not (target.Path = source.Path) then target.Path <- source.Path
if source.NodeType = TreeFile then tree.UpdateRenditions source target
member internal tree.SyncStep() =
match tree.State with
| TreeNew a ->
let target = tree.CreateBFromA
tree.UpdateITree a target
//tree.BTree.AddChild target
| TreeBoth(a,b) ->
let target = b
tree.UpdateITree a target
//tree.BTree.ModifyChild target
| TreeDeleted b ->
()
//tree.BTree.DeleteChild b
member t.Sync() =
t.Compare()
//t.BTree.Commit()
// Now I want to synchronize between a tree of type T and a tree of type U
let pt = new TProvider()
let ut = new UProvider()
let c = TreeComparison.Create(pt, "/start", ut , "/path")
c.Sync()
Das Problem dreht sich wahrscheinlich um CreateSubtree. Wenn Sie entweder auskommentieren:
DasdocTree.Compare()
LinieDastree.UpdateITree
Anrufeund ersetzen Sie sie durch()
, dann bleibt die Schlussfolgerung generisch und alles ist schön.
Das war ein ziemliches Rätsel. Ich habe versucht, die "Vergleichs" -Funktionen im zweiten Block aus dem Typ zu entfernen und sie als rekursive Funktionen zu definieren. Ich habe Millionen Möglichkeiten ausprobiert, die Eingabe zu kommentieren oder zu erzwingen. Ich verstehe es einfach nicht!
Die letzte Lösung, die ich in Betracht ziehe, ist eine vollständig separate (und duplizierte) Implementierung des Vergleichstyps und der Funktionen für die Untersynchronisation. Aber das ist hässlich und schrecklich.
Vielen Dank, wenn Sie so weit gelesen haben! Meine Güte!