6

I have a module with a function with the following signature:

module Something =
    let someFunc func = // ('TType -> 'TField) -> 'TValue
        ...

and inside that function I invoke a function from some external library which has a method with following signature (C#):

class SomeClass
{
    public ReturnType<TType> SomeMethod<TField>(func: Expression<Func<TType, TField>>) { ... }
}

When I try to pass a 'TType -> 'TField function there I get an error that it isn't convertible to Expression<Func<'TType, 'TField>>. I found the following question on StackOverflow: question

But it doesn't resolve my issue (The First answer didn't work, the second one works, but I have to change the signature of my function).

With the second answer I have to change the signature of my function to the following:

module Something =
    let someFunc func = // Expression<Func<'TType, 'TField>>) -> 'TValue
        ...

Add additional class visible for a "client" of my module, which looks like this:

type ExpressionHelper() =
    static member AsExpression<'TType, 'TField>(e: Expression<Func<'TType, 'TField>>) = e

So the final invocation instead looking like this:

let _ = Something.someFunc (fun (o: SomeType) -> o.someField)

looks like that:

let _ = Something.someFunc (ExpressionHelper.AsExpression (fun (o: SomeType) -> o.SomeField))

I don't wanna force the user of my module to convert F# function to Expression<Func<'TType, 'TField>> explicitly. I wanna do that inside my module, is there some way to achieve that?

MNie
  • 1,347
  • 1
  • 15
  • 35

1 Answers1

8

If you have a value of type 'T1 -> 'T2, there is no way you can turn it into a value of type Expression<Func<'T1, 'T2>>. This is not possible, because the former is a compiled function (a delegate referring to some object and its method), while the latter is a representation of the original source code.

So, you will need to use Expression<...> as the type of the argument to make this work (or Expr, which is the F# equivalent if you were to use quotations).

However, there are cases in F# where the compiler automatically turns a value created using the lambda function syntax fun x -> .. to a value of type Expression<...>. It does not do this for arguments of let-bound functions, but it does do this for the arguments of static methods. This means that you can use:

open System
open System.Linq.Expressions

type A = 
  static member foo (f:Expression<Func<int, int>>) = 
    f.ToString()

A.foo (fun n -> n + 1)
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • this is what I was worry about :(. I did what you propose at first (see the bottom of my question). But I wasn't fully happy with that solution. Nevertheless, thanks for the answer, since it isn't possible I'm gonna accept it. – MNie Mar 23 '20 at 06:11
  • @MNie Yeah, your `ExpressionHelper.AsExpression` works because it is also a static method - but is there a reason why you cannot make your `Something.someFunc` a static method too? Even if `Something` is a module name, you should be able to define a type with the same name and have the static method there (if you mark the module with the `CompilationRepresentationFlags.ModuleSuffix` attribute) – Tomas Petricek Mar 23 '20 at 20:53