我在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 IS 一个Stream我知道我可以把它像流一样投射到:
ms :> Stream
同样适用于[|"string"|]
和IEnumerable<string>
它实现了接口,我可以显式地转换为它但它不能自动工作。
为什么这样做?
let things:(IEnumerable<'a> -> 'a) = (fun f -> f.First())
let thing= things([|"";""|])
这也是自动上传!
答案 0 :(得分:12)
我认为尼古拉斯的答案一般都是正确的。允许在语言中自动向上到处会导致类型推断出现问题。
原则上,编译器可以尝试寻找在不同分支中返回的类型的公共基类型,但这并不像听起来那么容易:
首先,它应该返回最具体的类型还是其他类型? (编译器可以找到最具体的,但实际上你可能想要返回比你的代码推断的更通用的东西 - 所以明确指定它是有用的。)
其次,界面变得困难。想象一下,两个分支返回两个不同的类,它们都实现接口IA
和IB
。编译器如何确定返回类型应该是IA
还是IB
,还是obj
? (这是一个大问题,因为它会显着影响代码的使用方式!)有关详细信息,请参阅this snippet。
但是,有一个地方这不是问题,F#编译器允许它。也就是说,当将参数传递给函数或方法时 - 在这种情况下,编译器知道所需的类型是什么,因此它只需要检查允许upcast;它不需要推断要插入的upcast。因此,类型推断不受影响,因此编译器可以插入upcast。这就是为什么以下工作:
// 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>
。编译器知道要插入的upcast(因为它知道目标类型),所以它就是这样做的。
答案 1 :(得分:11)
这是具有强大的类型推断机制的对手方: 通过明确所有内容,编译器可以更容易来推断出真实与否。
起初感觉很奇怪,因为我们非常依赖其他轻松语言中的转换。
但实际上它总体上是一种强度,并允许所提到的类型推断,以及促进良好的编程实践,如编程到接口VS具体实现。
在案例中,一个有用的构造,它感觉简单多余,是添加演员
//The cast will be determined by the compiler, because of _
result :> _