【问题标题】:How to make F# infer common base type?如何让 F# 推断通用基类型?
【发布时间】:2012-03-27 15:56:15
【问题描述】:

考虑一下:

module Module1 =
    type A() = class end
    type B() = inherit A()
    type C() = inherit A()

    let f x = if x > 0 then new B() else new C()

最后一行产生了关于预期类型 B 的错误,但找到了类型 C。 好的,我可以假装理解:编译器不知道要推断哪个公共基础,以防万一。

但是你猜怎么着?即使我指定了函数类型,它仍然不起作用:

    let f x : A = if x > 0 then new B() else new C()

现在这给了我两个错误:“A 预期,B 找到”和“A 预期,C 找到”。 怎么回事?为什么它看不到 B 和 C 都可以隐式转换为 A?

是的,我知道我可以使用upcast,就像这样:

    let f x : A = if x > 0 then upcast new B() else upcast new C()

但是猜猜(再次)是什么? upcast 仅在存在显式函数类型声明的情况下有效! 换句话说,这是:

    let f x = if x > 0 then upcast new B() else upcast new C()

还是报错。

WTF?!我真的必须在我的程序中添加 50% 的噪音来帮助编译器吗? 为何大肆宣传 F# 代码干净无噪音?

不知何故,这似乎不是真的。 所以问题是:我错过了什么吗?如何使这既紧凑又有效?

【问题讨论】:

  • 子类型和类型推断不能很好地相处。
  • @OnorioCatenacci - 我不代表 Don 或团队发言。但是,我不确定是否建议在出现有关设计决策的问题时向 fsbugs 发送电子邮件 - 我认为这确实是用于错误报告和功能建议。
  • F# 确实试图通过提供object expressions 等替代方案来缓解(或引导您远离)此类情况。
  • @OnorioCatenacci:我并不是真的要建议改变设计。我最初的假设是设计并没有真正的限制,并且有一些简单的方法可以实现我想要的。然而,我现在从答案中看到,这确实是一个公认的限制,并且它或多或少有一些合理的理由支持。这就是我想知道的。
  • @Daniel:也许我应该更清楚一点,但我在常规程序设计过程中并没有遇到这种情况。我在尝试与 .NET 的其余部分进行互操作时遇到了这个问题,我不得不根据某些条件返回不同的派生对象。

标签: visual-studio-2010 inheritance f# type-inference


【解决方案1】:

类型推断和子类型不能很好地结合在一起,正如 Carsten 的链接在某种程度上讨论的那样。听起来您对 F# 的方法不满意,如果

if b then 
    e1 
else 
    e2

被隐式处理得更像

if b then (e1 :> 'a) else (e2 :> 'a)

编译器还根据 e1e2 推断的类型另外推断 'a 是类型层次结构中的最小上限。

这在技术上可能是可行的,我无法明确说明为什么 F# 不能以这种方式工作,但这里有一个猜测:如果 if 语句以这种方式运行,那么它永远不会是错误在ifelse 分支中有不同的类型,因为它们总是可以通过隐式向上转换为obj 来统一。然而,在实践中,这几乎总是一个程序员错误——你几乎总是希望类型相同(例如,如果我从一个分支返回一个字符,从另一个分支返回一个字符串,我可能打算从两个分支返回字符串,而不是 @ 987654331@)。通过隐式向上转换,您只会使这些错误的存在更难找到。

此外,在 F# 中处理复杂的继承层次结构相对较少,除非与其他 .NET 代码进行互操作。因此,这在实践中是一个非常小的限制。如果您正在寻找比upcast 语法更短的解决方案,您可以尝试:> _,只要有一些东西可以限制类型(无论是对整体结果的注释,还是对一个特定的转换)的分支)。

【讨论】:

  • 它不必是“任何基类”。正如我在问题中所写,当共同基础不明确时,我可以假装理解这一点。但是在我的第二个示例中,我明确指定f 的类型为A,假设这是我需要的类型是安全的,不是吗?换句话说,编译器可以把每个表达式都当作结尾有:> _
  • @FyodorSoikin:我可能错了,但类型注释仅用于帮助类型推断。如果它们按照您所说的那样工作,它们将改变程序的语义,因为它们将用于约束类型,而不仅仅是使其显式。综上所述,这似乎是一个根本性的变化。
  • @FyodorSoikin - 也许吧。一个问题可能是类似的约束一直从其他上下文传播(例如,如果我执行(if true then 'c' else "string").ToString(),那么编译器是否会向obj 插入一个隐式强制,因为这是ToString() 的来源,并且处理方式相同方式作为用户输入的约束?)。总体而言,对于在实践中不会出现太多情况的情况,将推理机制复杂化是否有意义?
  • @FyodorSoikin - 通常,在尝试将类型推断应用于支持继承和方法重载的语言时,需要做出妥协。即使对于非平凡的继承层次结构更为常见的 C#,也会出现类似的问题:stackoverflow.com/questions/4087304/…
  • @kvb:好的,我现在看到了[或多或少]这个限制背后的正当理由。而且,更重要的是,我明白这不是一个疏忽,而是一个有意识的决定。谢谢。
【解决方案2】:

这一切都是有原因的,但简而言之:F# 的类型比 C# 更强,所以你必须知道要转换到哪里(参见 here):

let f x = if x > 0 then (new B() :> A) else (new C() :> A)

在这里您可以找到更多信息:F# need for cast

这是另一个很棒的discussion

【讨论】:

  • 你能不能长一点?这一切的原因是什么?
  • 是的,我读过这些。简而言之,他们都说F#就是这样,所以处理它。我认为这个特定的设计决策没有任何客观原因 - 向上转型并不是隐含的。
  • 嗯,这和你想要一个好的类型干扰的事实 - 有一篇论文让这个更清楚,但我现在找不到它......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-01
  • 2023-03-25
  • 2013-05-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多