【问题标题】:Can someone explain these F# type inference oddities?有人可以解释这些 F# 类型推理的怪异之处吗?
【发布时间】:2021-05-12 09:32:22
【问题描述】:

我在使用 F# 的类型推断(或者说缺乏推断)时遇到了很多无法解释的行为。

我希望下面的所有 value 函数在给定 Foo of T 的情况下都会返回正确的类型 T,但在某些情况下它会返回 obj

type Foo<'T>(v:'T) =
    member __.Value = v // 'T
    static member (~~) (foo:Foo<_>) = foo.Value // Foo<'a> -> 'a

// should know the type but not always!
~~(Foo 1)                                       // int
let value (u:Foo<_>) = u.Value                  // Foo<'a> -> 'a
let value (u:Foo<_>) = ~~u                      // Foo<obj> -> obj
let inline value (u:Foo<_>) = ~~u               // Foo< ^a> -> ^a

let (~~) (foo:Foo<_>) = (Foo<_>.(~~))foo        // Foo<'a> -> 'a
let value (u:Foo<_>) = ~~u                      // Foo<'a> -> 'a

let inline (~~) (x:^a) : ^b = 
    (^a : (static member (~~) : ^a -> ^b) x)    // ^a -> ^b
let value (u:Foo<_>) = ~~u                      // Foo<obj> -> obj

let inline (~~) (x:^a) : 'b = 
    (^a : (static member (~~) : ^a -> 'b) x)    // ^a -> 'b
let value (u:Foo<_>) = ~~u                      // Foo<'a> -> 'a    

发生了什么事??

【问题讨论】:

  • 您的问题到底是什么?我将您的代码复制粘贴到 F# 编辑器中,一切看起来都很好(除了它正在重新定义同名的值,这是不允许的)。
  • 我原以为可以推断出正确的类型,但正如您从签名中看到的那样,有时会返回 obj
  • 感谢您的澄清!现在有道理了!你能否在你的问题中更明确地说出来,以确保它不会被关闭?
  • 确实,let value (u:Foo&lt;_&gt;) = ~~u 的推断正在失去泛型类型。但是为什么使用~~ 运算符而不是Value 成员(推理适用)?您是否想隐藏Value(将其设为private)以仅使用~~ 访问它?
  • @RomainDeneau 不,我想保存宝贵的手指动作,但最终我想了解幕后发生的事情。

标签: f# type-inference


【解决方案1】:

我不知道为什么某些涉及调用您的 op_TwiddleTwiddle 运算符定义的函数不能被概括。另一方面,这里确实发生了一些有趣的事情。让我们首先将代码转换为避免重新定义顶级绑定的形式:

type Foo<'T>(v:'T) =
    member __.Value = v // 'T
    static member (~~) (foo:Foo<_>) = foo.Value     // Foo<'a> -> 'a

module M1 =
    let value (u:Foo<_>) = ~~u                      // Foo<obj> -> obj

module M2 =
    let inline value (u:Foo<_>) = ~~u               // Foo< ^a> -> ^a

module M3 =
    let (~~) (foo:Foo<_>) = (Foo<_>.(~~))foo        // Foo<'a> -> 'a
    let value (u:Foo<_>) = ~~u                      // Foo<'a> -> 'a

module M4 =
    let inline (~~) (x:^a) : ^b = 
        (^a : (static member (~~) : ^a -> ^b) x)    // ^a -> ^b
    let value (u:Foo<_>) = ~~u                      // Foo<obj> -> obj

module M5 =
    let inline (~~) (x:^a) : 'b = 
        (^a : (static member (~~) : ^a -> 'b) x)    // ^a -> 'b
    let value (u:Foo<_>) = ~~u                      // Foo<'a> -> 'a

查看Foo&lt;'T&gt;类型的编译形式,很明显操作符为其返回类型取了一个类型参数:

public class Foo<T>
{
    internal T v;

    public T Value
    {
        get
        {
            return this.v;
        }
    }

    public Foo(T v) : this()
    {
        this.v = v;
    }

    public static a op_TwiddleTwiddle<a>(Foo<a> foo)
    {
        return foo.v;
    }
}

这无关紧要,因为除了M2 内联定义之外,没有一个示例实际上调用了op_TwiddleTwiddle 运算符。所有其他示例都通过直接访问 Value 属性的支持字段进行了优化,尽管我们希望它是透明的,不会影响泛化。

其他有趣的案例是

  • M3,其中运算符 Foo&lt;_&gt;.(~~) 的注解提高了泛化能力,并且
  • M5,因为消除了第二个(不必要的)静态解析类型变量也使它工作得更好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-05
    • 1970-01-01
    • 2011-12-01
    • 2010-10-28
    相关资源
    最近更新 更多