Nos estamos poniendo peludos aquí. He probado un montón de código de sincronización de árboles en representaciones concretas de datos, y ahora necesito abstraerlo para que pueda ejecutarse con cualquier fuente y destino que admita los métodos correctos. [En la práctica, esto será fuentes como Documentum, jerarquías de SQL y sistemas de archivos; con destinos como Solr y una tienda de referencia cruzada SQL personalizada.]Obligar a la inferencia de tipo F # sobre genéricos e interfaces para mantenerse suelto
La parte difícil es que cuando estoy recursiva abajo de un árbol de tipo T
y la sincronización en un árbol de tipo U
, en ciertos archivos que necesito hacer una "sub-sincronización" de un segundo tipo V
a ese tipo U
en el nodo actual. (V
representa la estructura jerárquica dentro de un archivo ...) Y el motor de inferencia tipo en F # me está dando vueltas en círculos, tan pronto como trato de agregar la sub-sincronización a V
.
Lo estoy representando en un TreeComparison<'a,'b>
, por lo que las cosas anteriores dan como resultado un TreeComparison<T,U>
y una subcomparación de TreeComparison<V,U>
.
El problema es que tan pronto como yo proporciono un concreto TreeComparison<V,'b>
en uno de los métodos de la clase, el tipo V
se propaga a través de la totalidad de la deducción, cuando quiero que el parámetro primer tipo genérico para quedarse (when 'a :> ITree
). Tal vez hay algo de tipeo que puedo hacer en el valor TreeComparison<V,'b>
? O, más probablemente, la inferencia en realidad me está diciendo que algo está inherentemente roto en la forma en que pienso sobre este problema.
Esto fue realmente complicado de comprimir, pero quiero dar código de trabajo que pueda pegar en un script y experimentar, por lo que hay un montón de tipos al principio ... el núcleo está justo al final si quiero saltear La mayor parte de la comparación y recursión real entre los tipos a través de ITree ha sido cortada porque no es necesario ver el problema de inferencia con el que me estoy golpeando.
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()
El problema probablemente gira en torno a CreateSubtree. Si usted comenta efectuó:
- Los
docTree.Compare()
línea - Las llamadas
tree.UpdateITree
y reemplazarlos con ()
, entonces la inferencia se mantiene genérico y todo es precioso.
Esto ha sido todo un acertijo. Intenté mover las funciones de "comparación" en el segundo fragmento fuera del tipo y definirlas como funciones recursivas; He intentado un millón de formas de anotar o forzar la escritura. ¡Simplemente no entiendo!
La última solución que estoy considerando es hacer una implementación completamente separada (y duplicada) del tipo de comparación y funciones para la sub-sincronización. Pero eso es feo y terrible.
¡Gracias si leíste esto! Sheesh!
Nota: Realmente no entiendo los casos de uso para los params de tipo resueltos estáticamente, como^a - tal vez este sea uno de ellos? –