views:

120

answers:

3

Hi,

In F# I have a function that returns System.Linq.Expression instances:

and System.Object with
  member this.ToExpression() = 
    match this with
    | :? System.Int32 -> Expression.Constant(this) :> Expression
    | :? System.Boolean -> Expression.Constant(this) :> Expression
    | :? Tml.Runtime.Seq as s -> s.ToExpression()
    | _ -> failwith "bad expression"

If I omit the type coercions on the return values F# will infer the return type of the function to ConstantExpression. My first thought was to explicitly mark the return type as being : #Expression, but that didn't work. Is there a more elegant way of doing this that doesn't involve manually casting return types to the most generic type?

Thanks.

Edit: Thanks to all of you for the answers. I'll go with the explicit return type + upcast scenario.

+2  A: 

Here are a couple ways you might prefer:

open System.Linq.Expressions 

type System.Object with
    member this.ToExpression() : Expression =  // explicit
        match this with 
        | :? System.Int32 -> upcast Expression.Constant(this) // upcast
        | :? System.Boolean -> Expression.Constant(this) :> _ // _
        | _ -> failwith "bad expression"

By explicitly stating the return type on the member declaration, you can then infer it in the body, e.g. via _ as a "please infer this type for me" or by using the upcast operator which will infer the type to up-cast to from the constraints.

Brian
+1  A: 

I don't think there is any significantly more elegant way of writing this, unfrotunately.

The compiler requires that all branches of the match expression will have the same return type and it doesn't implicitly insert any coercions. You can use the upcast keyword to insert a coercion without specifying the target type - in this case, the compiler will use other information (such as type annotations) to determine the type and you won't have to repeat the type:

and System.Object with 
  member this.ToExpression() : Expression =  
    match this with 
    | :? System.Int32 -> upcast Expression.Constant(this) 
    | :? System.Boolean -> upcast Expression.Constant(this)
    | :? Tml.Runtime.Seq as s -> upcast s.ToExpression() 
    | _ -> failwith "bad expression" 

I added type annotation and upcast to each of the expression and type annotation, so the F# compiler infers that the upcast needs to coerce the result to Expression. The only place where the compiler inserts implicit coercions is when calling a function, so you could also write the following (but I'm not sure if it's any better):

// Thanks to implicit coercions, we don't even need #type
let expr (a:Expression) = a

// and then for example:
| :? System.Int32 -> Expression.Constant(this) |> expr

For some reason, upcast is a keyword, so you cannot use it with pipelining, so definining a function like this may have some benefits.

Tomas Petricek
+1  A: 

Strictly speaking this is not removing the coersion but in my opinion it's a bit better on the eye (and will save you a little bit of typing too :) )

open System.Linq.Expressions

let Constant obj = Expression.Constant(obj) :> Expression

type System.Object with
    member this.ToExpression()
        match this with 
        | :? System.Int32 -> Constant(this)
        | :? System.Boolean -> Constant(this)
        | _ -> failwith "bad expression"

since the type of Constant is a'->Expression it will work in both cases. The down side is of cause that you will have to define a function for each of the Expression factory methods you are going to use.

Rune FS