Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't up-casting automatic in f#

Tags:

casting

f#

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!

like image 798
albertjan Avatar asked May 29 '13 07:05

albertjan


People also ask

Is Upcasting automatic?

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.

Is downcasting possible in C#?

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.

What is Upcasting and downcasting in C#?

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.


2 Answers

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.

like image 156
Tomas Petricek Avatar answered Oct 19 '22 01:10

Tomas Petricek


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 :> _
like image 28
nicolas Avatar answered Oct 19 '22 03:10

nicolas