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 开发者_StackOverflow中文版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
- Intuition suggests that this is a result of a more general issue. I would have thought though that something as common as pattern matching, or if statements which also exhibit the same property, would have a type checking rule to account for common super types.
- Before anyone suggests - I appreciate that if A or B were Object Expressions this would work, but my real example is creating instances of C# classes where they are normal classes.
- Is there a way for me to declare functions to implicitly convert types, as for example scala has, so I could apply automatic conversions for the module where I'm doing this generation?
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).
精彩评论