views:

177

answers:

1

I am creating Linq expression trees from F# that operates on a custom datatype I have. The type is a very simple discriminated union that has the usual arithmetic operators overloaded. But for some reason I cannot create arithmetic linq expression nodes due to the fact that it can't find the correct overload. Thing is, I swear I had this working some time ago but I can't figure out what I changed to make it break.

I'll attach a small code sample showing the problem. The datatype below has the Addition operator overloaded. Using the overloaded operator works like a charm, but when I try to create an addition expression tree node using Expression.Add(lhs, rhs) the system throws an exception complaining that it can't find the overload for the Add operation.

Does anyone have an idea of what I am doing wrong?

Thank you, Rickard

open System.Linq.Expressions

module DataType =
    exception NotImplementedYet of string

    type DataCarrier =
        | ScalarCarrier of float
        | VectorCarrier of float array

        member this.Add(other) =
            match (this, other) with
            | ScalarCarrier(x), ScalarCarrier(y) -> ScalarCarrier(x + y)
            | VectorCarrier(u), VectorCarrier(v) -> 
                VectorCarrier(Array.map2 (fun x y -> x + y) u v)
            | _,_ -> raise (NotImplementedYet("No go!"))

        static member (+) (lhs:DataCarrier, rhs) =
            lhs.Add(rhs)

module Main =
    let createAddOp (lhs:DataType.DataCarrier) (rhs:DataType.DataCarrier) =
        let clhs = Expression.Constant(lhs)
        let crhs = Expression.Constant(rhs)
        Expression.Add(clhs, crhs)

(* no problems with this one *)
printf "Testing operator overloading: %A" (DataType.ScalarCarrier(1.0) 
                                           + DataType.ScalarCarrier(2.0))
(* this throws an exception *)
printf "Testing expr construction %A" (Main.createAddOp 
                                        (DataType.ScalarCarrier(1.0))
                                        (DataType.ScalarCarrier(2.0)))
+3  A: 

One solution is to explicitly type the Expression operands (giving them the static type DataType.DataCarrier instead of their runtime type DataType.DataCarrier.ScalarCarrier):

module Main =
    let createAddOp (lhs:DataType.DataCarrier) (rhs:DataType.DataCarrier) =
        let clhs = Expression.Constant(lhs, typeof<DataType.DataCarrier>)
        let crhs = Expression.Constant(rhs, typeof<DataType.DataCarrier>)
        Expression.Add(clhs, crhs)

Another option would be to explicitly pass the addition operator to use:

module Main =
    let createAddOp (lhs:DataType.DataCarrier) (rhs:DataType.DataCarrier) =
        let clhs = Expression.Constant(lhs)
        let crhs = Expression.Constant(rhs)
        Expression.Add(clhs, crhs, typeof<DataType.DataCarrier>.GetMethod("op_Addition"))

I am surprised that your original code doesn't work, though. It appears to be a limitation in how expression trees find relevant add operators (that is, it appears that Linq only looks for add operators on the runtime types of operands).

kvb
Fantastic, I really thought there were no workarounds. You just saved me a lot of stress! +1I think that this used to work before the Oct CTP of F# though. I'll do some testing.
Rickard