【问题标题】:Why doesn't type inference determine automatically that property exists on type?为什么类型推断不能自动确定类型上存在属性?
【发布时间】:2017-02-22 13:52:27
【问题描述】:

我需要比较不同类型的对象,我知道它们都具有属性 Id 和 Legacy_id。不幸的是,我无法向它们添加接口,因为类型来自数据库模式。我希望以下比较器可以工作:

type Comparer<'T >()=
  interface System.Collections.Generic.IEqualityComparer<'T> with 
    member this.Equals (o1:'T,o2:'T)=
      o1.Legacy_id=o2.Legacy_id
    member this.GetHashCode(o:'T)=
      o.Id+o.Legacy_id

我也有比较器类型的实例化类型。所以,理论上编译器有足够的信息。

但它给出了一个错误:“基于此程序点之前的信息查找不确定类型的对象。在此程序点之前可能需要类型注释来限制对象的类型。这可能允许查找解决。”

我想知道为什么 F# 在这里失败了?是否有任何实际/理论上的限制,或者只是没有实施?这种推断可能非常有用。

我怀疑有关 F# 编译器的解释只是向前走。 C# 没有限制。这就是错误消息所抱怨的。是这样吗?

【问题讨论】:

  • 这不能被编译,因为任何给定的'T 不保证有一个名为Id 的属性。这是一个思想实验:如果你将这个类实例化为Comparer&lt;int&gt;,会发生什么?
  • @FyodorSoikin 但我没有将它实例化为 Comparer。所以,在我用正确的类型实例化它之前,编译可以工作。
  • 这在 C# 中也不起作用,因为 'T 没有 Id 属性。 C#/F# 泛型只进行一次类型检查,并且不会像 C++ 模板那样根据它们的用途进行扩展。
  • F# 类型推断功能强大,但并不完美。它不能做的一件事是推断“伪接口”,例如“所有这些类都有一个 Id 属性”。这部分是因为它与.Net 类型系统相关联,该系统不允许此类伪接口。而且,F# 编译器希望保证安全。并且仅仅因为你不实例化Comparer&lt;int&gt;现在,并不意味着编译器可以知道你在未来不会这样做。由于它不能保证这一点,它不能假设'T 将始终具有Id 属性。
  • 我想我找到了答案。这似乎是可能的。它是关于鸭子类型和编译时多态性的。在我尝试将其应用于我的案例时,请稍候。 stackoverflow.com/questions/7065939/f-and-duck-typing

标签: f# type-inference


【解决方案1】:

成员约束不能用于类型,这就是你不能这样做的原因。见here

您可以做的是创建一个比较器类,该类接受特定类型的显式相等检查和哈希码生成函数,例如

type Comparer<'T>(equalityFunc, hashFunc) =
  interface System.Collections.Generic.IEqualityComparer<'T> with 
    member this.Equals (o1:'T,o2:'T)=
      equalityFunc o1 o2
    member this.GetHashCode(o:'T)=
      hashFunc o

然后您可以使用内联函数为与您希望施加的约束匹配的类型生成上述实例:

let inline id obj =
    ( ^T : (member Id : int) (obj))

let inline legacyId obj =
    ( ^T : (member Legacy_id : int) (obj))

let inline equals o1 o2 =
    id o1 = id o2

let inline hash o =
    id o + legacyId o

let inline createComparer< ^T when ^T : (member Id: int) and ^T : (member Legacy_id : int) >() =
    Comparer< ^T >(equals, hash) :> System.Collections.Generic.IEqualityComparer< ^T >

假设您有一些类型 TestType,它具有两个必需的属性:

type TestType =
   member this.Legacy_id = 7
   member this.Id = 9

然后,您可以使用 createComparer&lt;TestType&gt;() 来生成适合您类型的相等比较器。

【讨论】:

  • 因此,C# 中的泛型类型不是静态编译的。我一直想知道为什么不可能像 C++ 那样对类型进行操作。现在看来我明白了。因此,拥有 F# 的 ^T 我可能希望在 F# J 中重新实现 Aleksandrescu 的模板元编程内容
  • @alehro F# 的静态解析类型参数仍然远不如 C++ 模板灵活。例如,没有办法像您可能使用的那样对值进行参数化,例如template &lt;int N&gt; 但是它们确实允许模拟更高种类的多态性。
【解决方案2】:

获取比较器的简洁方法,例如创建HashSet 是:

let inline getId o = (^T : (member Id : int) o)
let inline getLegacyId o = (^T : (member Legacy_id : int) o)

let inline legacyComparer< ^T when ^T : (member Id : int) and ^T : (member Legacy_id : int)>() =
    { new System.Collections.Generic.IEqualityComparer<'T> with
          member __.GetHashCode o = getId o + getLegacyId o
          member __.Equals(o1, o2) = getLegacyId o1 = getLegacyId o2 }

类型推断是关于推断 a 类型,而不是证明某些泛型类型参数满足所有实例化的任意条件(请参阅名义与结构类型系统)。关于 SO 上的静态解析类型参数已经有很多很好的答案了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 2019-08-31
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 2015-10-31
    • 1970-01-01
    相关资源
    最近更新 更多