Problem Summary
At the moment when using f# I must explicitly coerce a value to the parent type of its type in order to get pattern matching expressions to type check correctly. I would ideally like a neater way of doing.
Example
Suppose I have some class hierachy:
type Foo () =
abstract member Value : unit -> string
type A (i:int) =
inherit Foo ()
override this.Value () = i.ToString()
type B (s:string) =
inherit Foo ()
override this.Value () = s
Ideally, and in some programming languages in normally, I would write the equivalent of the following:
let bar (i:int) : Foo =
match i with
| 1 -> B "one"
| _ -> A i
However this fails to type check correctly, giving me the error, "This expression was expected to have type Foo but here has type B". I don't understand why the compiler doesn't have enough information to infer a common super type for the match expression and then check that the common super type is 'Foo'.
At present I am forced to provide an explicit coercion for every case in the pattern match:
let bar2 (i:int) : Foo =
match i with
| 1 -> (B "one") :> Foo
| _ -> (A i) :> Foo
I would like to avoid this.
Further Notes
Thanks for any help on this matter.
I would use upcast
, a la
[<AbstractClass>]
type Foo () =
abstract member Value : unit -> string
type A (i:int) =
inherit Foo ()
override this.Value () = i.ToString()
type B (s) =
inherit Foo ()
override this.Value () = s
let bar2 i : Foo =
match i with
| 1 -> upcast B "one"
| _ -> upcast A i
You still have to add it to every branch, but this is often preferable to casting to the type, since often the typename is like 20 or 30 characters long (MyNamespace.ThisThingy
), whereas upcast
is just 6 characters.
But, briefly, the language rules don't allow for anything else, the types of all the branches have to be equal.
I've seen this question a couple of times before, but I just realized that there is quite an interesting way to workaround the issue (without any significant negative effects such as big runtime overhead).
You can use a very simple computation expression that has only Return
member. The builder will have a type parameter and Return
will expect values of this type. The trick is, that F# does insert automatic upcasts when calling a member. Here is the declaration:
type ExprBuilder<'T>() =
member x.Return(v:'T) = v
let expr<'T> = ExprBuilder<'T>()
To write a simple pattern matching that returns anything as obj
, you can now write:
let foo a = expr<obj> {
match a with
| 0 -> return System.Random()
| _ -> return "Hello" }
You still have to be explicit about the return type (when creating the computation expression), but I find the syntax quite neat (but it is definitely a tricky use that could confuse people who'll see it for the first time).
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