Dan*_*tch 11 generics f# type-inference interface
我们这里真的很开心.我已经在数据的具体表示上测试了一堆树同步代码,现在我需要对它进行抽象,以便它可以运行任何支持正确方法的源和目标.[实际上,这将是Documentum,SQL层次结构和文件系统等来源; 使用Solr和自定义SQL交叉引用存储等目标.
棘手的部分是,当我递归一个类型的树T并同步到一个类型的树时U,在某些文件中,我需要在当前节点处对该类型V执行第二种类型的"子同步" U.(V代表阶层结构的内部文件......),并在F#的类型推理引擎将立即围绕推动我在圈子里就这一点,因为我尝试添加子同步到V.
我在一个代表这个TreeComparison<'a,'b>,所以上面的东西导致a TreeComparison<T,U>和子比较TreeComparison<V,U>.
问题是,只要我TreeComparison<V,'b>在其中一个类方法中提供具体V,当我希望第一个类型参数保持泛型(when 'a :> ITree)时,类型会传播所有推断.也许我可以对TreeComparison<V,'b>价值做一些打字?或者,更有可能的是,推断实际上告诉我,在我思考这个问题的方式中,某些东西本来就被打破了.
这对于压缩来说真的很棘手,但是我想给你可以粘贴到脚本中的工作代码并进行实验,所以一开始就有很多类型......如果你想跳过,核心内容就在最后.通过ITree对大多数类型的实际比较和递归进行了切割,因为没有必要看到我正在敲打的推理问题.
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()
Run Code Online (Sandbox Code Playgroud)
问题可能围绕CreateSubtree.如果您注释掉:
docTree.Compare()条线tree.UpdateITree呼叫并替换它们(),然后推断保持通用,一切都很可爱.
这真是一个难题.我已经尝试将第二个块中的"比较"函数移出类型并将它们定义为递归函数; 我尝试了一百万种注释或强制打字方式.我只是不明白!
我正在考虑的最后一个解决方案是为子同步进行比较类型和函数的完全独立(和重复)实现.但那是丑陋而可怕的.
谢谢,如果你读到这么远!啧!
Bri*_*ian 17
我没有分析足够的代码来弄清楚原因,但补充说
member internal tree.SyncStep() : unit =
// ^^^^^^
Run Code Online (Sandbox Code Playgroud)
似乎解决了这个问题.
编辑
也可以看看
获得对F#类型推理算法的功能和限制的深入理解需要经验.但是这个例子似乎是人们在做非常高级的事情时遇到的一类问题.对于类的成员,F#推理算法就像这样
这可能不完全正确; 我不太清楚它描述算法,我只是对它有所了解.你总是可以去阅读语言规范.
经常发生的是你得到子弹3并迫使推理器开始尝试同时解决/约束所有方法体,而实际上它是不必要的,因为,例如,某些函数可能具有简单的具体固定类型.就像SyncStep是unit-> unit一样,但是F#在步骤3中还不知道它,因为签名不是明确的,它只是说好了SyncStep对于某些尚未解决的类型'a'而言类型为"unit - >'a"那么现在SyncStep现在通过引入一个不必要的变量而不必要地使所有解决方案复杂化.
我发现这个的方式是,第一个警告(这个构造导致代码不如类型注释所指示的那样通用.类型变量'a被约束为类型'V')位于体的最后一行调用docTree.Compare()时的UpdateRenditions.现在我知道Compare()应该是unit - > unit.所以,我怎么可能会得到一个有关通用岬警告存在?啊,好吧,编译器不知道返回类型是那个时候的单位,所以它必须是通用的东西不是.事实上,我可以将返回类型注释添加到Compare而不是SyncStep - 任何一个都可以.
无论如何,我很啰嗦.总结一下
希望有所帮助!
| 归档时间: |
|
| 查看次数: |
1732 次 |
| 最近记录: |