15

我在 F# 中实现了一个 C# 接口,它看起来像:

public interface IThings
{
    Stream ThingsInAStream()
}

我的实现看起来像:

type FSharpThings() = 
    interface IThings with
       member this.ThingsInAStream() = 
           let ms = new MemoryStream()
           // add things to stream
           ms

现在我收到消息:

The expression was expected to have type 
  Stream
but here has type
  MemoryStream

我不明白 MemoryStream一个流,我知道我可以将其转换为流,例如:

ms :> Stream

同样适用[|"string"|]IEnumerable<string>它实现了接口,我可以显式地转换为它,但它不能自动工作。

为什么这行得通?

let things:(IEnumerable<'a> -> 'a) = (fun f -> f.First())

let thing= things([|"";""|])

这也是自动向上转换!

4

2 回答 2

13

我认为 Nicolas 的回答总体上是正确的。允许在语言的任何地方自动向上转换会导致类型推断出现问题。

原则上,编译器可以尝试寻找不同分支中返回的类型的通用基类型,但这并不像听起来那么容易:

  • 首先,它应该返回最具体的类型还是其他类型?(编译器可以找到最具体的,但也许您实际上想要返回比从您的代码中推断出的更通用的东西 - 因此明确指定它很有用。)

  • 其次,接口变得困难。想象一下,两个分支返回两个不同的类,它们都实现了接口IAIB. 编译器如何决定返回类型应该是IAorIB还是可能obj?(这是一个大问题,因为它显着影响了代码的使用方式!)有关更多详细信息,请参阅此代码段

但是,有一个地方这不是问题,F# 编译器允许这样做。也就是说,当将参数传递给函数或方法时——在这种情况下,编译器知道所需的类型是什么,因此它只需要检查是否允许向上转换;它不需要推断要插入什么向上转换。结果,类型推断不受影响,因此编译器可以插入向上转换。这就是为什么以下工作:

// The example from the question
let first (items:seq<'a>) = items |> Seq.head
let thing = first [|"";""|]

// Even simpler example - passing string as object
let foo (a:obj) = a 
foo "123"

在这里,参数是array<string>并且函数期望seq<string>。编译器知道要插入什么向上转换(因为它知道目标类型),所以它会这样做。

于 2013-05-29T13:16:35.223 回答
11

这是拥有强大类型推断机制的对手:通过明确所有内容,编译器更容易推断出什么是真实的或不真实的。

起初感觉很奇怪,因为我们非常依赖其他轻松语言的这些转换。

但实际上它是一个整体优势,并且允许提到的类型推断,以及促进良好的编程实践,如对接口编程 VS 具体实现。

在感觉完全多余的情况下,一个有用的构造是添加演员表

//The cast will be determined by the compiler, because of _
result :> _
于 2013-05-29T10:05:02.487 回答