I was implementing a C# interface in F# that looked something like:
public interface IThings
{
Stream ThingsInAStream()
}
My implementation looked something like:
type FSharpThings() =
interface IThings with
member this.ThingsInAStream() =
let ms = new MemoryStream()
// add things to stream
ms
Now I get the message:
The expression was expected to have type
Stream
but here has type
MemoryStream
I don't understand MemoryStream IS a Stream I know that I can cast it to as stream like:
ms :> Stream
Same goes for [|"string"|]
and IEnumerable<string>
it implements the interface and I can explicitly cast to it BUT it doesn't work automatically.
Why does this work?
let things:(IEnumerable<'a> -> 'a) = (fun f -> f.First())
let thing= things([|"";""|])
This is also automatic upcasting!
This is called upcasting. Upcasting is done automatically, while downcasting must be manually done by the programmer, and i'm going to give my best to explain why is that so.
Upcasting is legal in C# as the process there is to convert an object of a derived class type into an object of its base class type. In spite of the general illegality of downcasting you may find that when working with generics it is sometimes handy to do it anyway.
Upcasting converts an object of a specialized type to a more general type. Downcasting converts an object from a general type to a more specialized type. A specialization hierarchy of bank accounts.
I think the answer from Nicolas is generally right. Allowing automatic up-casting everywhere in the language would cause problems for type inference.
In principle, the compiler could try looking for a common base type of the types returned in different branches, but this is not as easy as it sounds:
First, should it return the most specific type or some other type? (The compiler could find the most specific, but perhaps you actually want to return something a bit more general than what could be inferred from your code - so specifying that explicitly is useful.)
Second, things get difficult with interfaces. Imagine that two branches return two different classes both implementing interfaces IA
and IB
. How does the compiler decide whether the return type should be IA
or IB
, or perhaps obj
? (This is a big problem, because it significantly affects how the code can be used!) See this snippet for more details.
However, there is one place where this is not a problem and F# compiler allows it. That is, when passing argument to a function or a method - in this case, the compiler knows what the desired type is and so it only needs to check that the upcast is allowed; it does not need to infer what upcast to insert. As a result, the type inference is not affected and so the compiler can insert an upcast. That is why the following works:
// 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"
Here, the argument is array<string>
and the function expects seq<string>
. The compiler knows what upcast to insert (because it knows the target type) and so it does that.
This is the counterparty of having a powerful type inference mechanism : By having everything explicit, it is easier for the compiler to reason about what is true or not.
It feels strange at first, as we are relying so much on those conversions in other relaxed langage.
But practically it turns out to be a strength overall, and allow both the type inference mentioned, as well as promotes good programming practices like programing to the interface VS the concrete implementation.
One useful construct in cases, where it feels plain superfluous, is to add the cast
//The cast will be determined by the compiler, because of _
result :> _
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With