在重构某些代码时,我注意到一种情况,即移动时代码会中断:
type ThingBase () = class end
and Functions =
static member Create<'T when 'T :> ThingBase and 'T : (new : Unit -> 'T)> () = new 'T ()
and Thing () =
inherit ThingBase ()
static member Create () = Functions.Create<Thing> ()
// This works, but try moving the Functions type here instead.
如果您将Functions
类型移到Thing
类型下方,代码会意外中断:
type ThingBase () = class end
and Thing () =
inherit ThingBase ()
static member Create () = Functions.Create<Thing> ()
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// This construct causes code to be less generic than indicated by the
// type annotations. The type variable 'T has been constrained to be
// type 'Thing'.
and Functions =
static member Create<'T when 'T :> ThingBase and 'T : (new : Unit -> 'T)> () = new 'T ()
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// The type ''T' does not match the type 'Thing'.
无论我尝试什么,我都无法做到这一点。为什么类型推断如此顽固,拒绝概括Create
方法。
顺便说一句,我也尝试了F#4.1 module rec
,如果Create
也是模块中的函数也没关系。
任何见解?对我而言,这似乎应该是编译器不应该遇到任何麻烦的事情。
答案 0 :(得分:6)
如果你这样做会编译
static member Create<'T when 'T :> ThingBase
and 'T : (new : Unit -> 'T)> () : 'T = new 'T ()
// ^^^^
其中明确说明了返回类型。
递归定义在两次传递中从左到右进行了类型检查;第一个函数/方法签名,然后是主体。您需要body(或显式返回类型注释)来获取结果类型,因此您需要首先使用body,否则需要注释以便在两次传递的第一次中解析它。
答案 1 :(得分:1)
我不知道为什么编译器在移动它时会过度约束Create
方法的类型参数。解决方法可以是intrinsic type extension,因此您可以将类型定义拆分为多个部分。这可以帮助避免递归依赖。
type ThingBase () = class end
type Thing () =
inherit ThingBase ()
type Functions =
static member Create<'T when 'T :> ThingBase and 'T : (new : Unit -> 'T)> () =
new 'T ()
type Thing with
static member Create () = Functions.Create<Thing> ()
答案 2 :(得分:1)
如果您想继续使用该模式,请按照以下步骤操作。我假设你想在基地里嵌入某种工厂模式。
顺便说一下,当@Fyodor从左到右说,这也意味着自上而下。所以...你也可能正在反对这一点,即使和功能在逻辑上应该正常工作。我也同意:更平坦的等级制度,但有时候,由于各种原因,我们无法获得选择的奢侈。
type ThingBase () = class end
and Thing () =
inherit ThingBase ()
and Functions() =
static member Create<'T when 'T :> ThingBase and 'T : (new : Unit -> 'T)> () = new 'T ()
and Thing with
static member Create () = Functions.Create<Thing> ()
// typically, I'd prefer
//type Thing with
// static member Create () = Functions.Create<Thing> ()
// or
//module Thing =
// let Create() = Functions.Create<Thing> ()
的引用:
答案 3 :(得分:0)
以下内容不正确。显然,递归定义会获得两次类型检查 - 一次用于签名,然后用于实现。我无论如何都要留下答案,仅供参考。
类型推断在一次传递中从左到右工作。一旦遇到对Functions.Create
的调用,就会确定签名必须是什么,后来的扩充无法改变。
这与xs |> Seq.map (fun x -> x.Foo)
有效的原因相同,但Seq.map (fun x -> x.Foo) xs
没有:在第一种情况下,x
的类型是从之前遇到的xs
知道的xs
,而在第二种情况下,它未知,因为还没有遇到org.apache.bval.cdi.BValExtension.active=false
。
P上。 S.你实际上并不需要一个递归组。您的类型之间没有递归依赖关系。