移动递归类型时代码中断

时间:2017-05-28 18:43:44

标签: f#

在重构某些代码时,我注意到一种情况,即移动时代码会中断:

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也是模块中的函数也没关系。

任何见解?对我而言,这似乎应该是编译器不应该遇到任何麻烦的事情。

4 个答案:

答案 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.你实际上并不需要一个递归组。您的类型之间没有递归依赖关系。