我们在这里开始长毛。我已经在数据的具体表示上测试了一堆树同步代码,现在我需要对它进行抽象,以便它可以运行任何支持正确方法的源和目标。 [实际上,这将是Documentum,SQL层次结构和文件系统等来源;使用Solr和自定义SQL交叉引用存储等目标。]
棘手的部分是,当我向下递归T
类型的树并同步到U
类型的树时,在某些文件中,我需要执行“子同步”当前节点的第二种类型V
到该类型U
。 (V
表示文件中的层次结构 文件...)而且,当我尝试添加子同步时,F#中的类型推理引擎正在围绕这个推动我的圈子到V
。
我在TreeComparison<'a,'b>
中表示这一点,因此上述内容会产生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()
问题可能围绕CreateSubtree。如果您注释掉:
docTree.Compare()
行tree.UpdateITree
来电并用()
替换它们,然后推断保持通用,一切都很可爱。
这真是一个难题。我已经尝试将第二个块中的“比较”函数移出类型并将它们定义为递归函数;我已经尝试了一百万种注释或强制打字方式。我只是不明白!
我正在考虑的最后一个解决方案是为子同步进行比较类型和函数的完全独立(和重复)实现。但那很丑陋。
如果你读到这里,谢谢!啧!
答案 0 :(得分:17)
我没有足够的分析代码来弄清楚原因,但是添加了
member internal tree.SyncStep() : unit =
// ^^^^^^
似乎解决了这个问题。
修改
另见
Understanding F# Value Restriction Errors
Unknown need for type annotation or cast
需要经验才能深入了解F#类推理算法的功能和局限性。但是这个例子似乎是人们在做非常高级的事情时遇到的一类问题。对于类的成员,F#推理算法执行类似
的操作这可能不完全正确;我不太清楚它描述算法,我只是对它有所了解。你总是可以阅读语言规范。
经常发生的是你到达子弹3并迫使推理器开始尝试同时解决/约束所有方法体,而事实上它是不必要的,因为例如也许某些功能有一个简单的具体固定类型。就像SyncStep是unit-&gt;单位一样,但是F#在步骤3中还不知道它,因为签名不是明确的,它只是说好了SyncStep对于某些尚未解决的类型有类型“unit - &gt;'a” a然后现在SyncStep现在通过引入一个不必要的变量而不必要地使所有解决方案复杂化。
我发现这个的方式是,第一个警告(这个构造导致代码不如类型注释所指示的那样通用。类型变量'a被约束为类型'V')在最后一行调用docTree.Compare()时UpdateRenditions的主体。现在我知道Compare()应该是unit - &gt;单元。那么我怎么可能收到有关泛型那里的警告?啊,好吧,编译器不知道返回类型是那个时候的单位,所以它必须是通用的东西不是。事实上,我可以将返回类型注释添加到Compare而不是SyncStep - 任何一个都可以。
无论如何,我很啰嗦。总结一下
希望有所帮助!
答案 1 :(得分:3)
这是一篇旧帖子,但它是我搜索的第一名结果。我有一些东西需要补充,可以帮助其他人在类型推理上挣扎,就像我(和OP)一样。
我发现将推理视为函数调用结构的指数函数,这些调用可能具有的签名以及它们可能没有的签名是有帮助的。考虑所有这三个因素非常重要。
只是为了踢,考虑这个函数有三个变量:sqrt(2 * 2 * 3)
很明显,很明显它会简化为无理数的2倍,这必须是圆形的(因此获得无限的不精确度),以使其在日常生活中有用。
F#版本反馈自身,将错误复合到“#34;舍入”#34;最终导致不良类型的推论。因为类型可能或可能不是这个等式的一个因素,所以不总是可以/容易地直接用类型注释来解决问题。
现在想象在两个问题函数之间添加一个额外的完全通用(即中性)函数,将我们的等式改为:sqrt(2 * 2 * 4)
突然间,结果非常合理,产生了完全准确的4值。相比之下,将反向相关的第一和第二值修改为1对我们来说绝对没有任何帮助。
如果它有可能破坏整个程序,那么不要害怕修改结构。一个额外的功能与你必须跳到(持续)弯曲F#的所有箍是一个非常小的代价,你很有可能找到一种方法来使额外的结构有用。在某些情况下,做上述事情可以将一个非常非常非常有争议的程序变成一个完美的小天使,为许多功能而来。