My guess is that it should be fixed in F# 3.1. This is from VS2013 Preview
type T = static member Get(e : System.Linq.Expressions.Expression<System.Action<'T>>) = e
type U = member this.MakeString() = "123"
T.Get(fun (u : U) -> ignore(u.MakeString())) // u => Ignore(u.MakeString())
UPDATE: Cannot check with actual library from the question, so I'd try to mimic the interface I see. This code works fine in F# 3.1
open System
open System.Linq.Expressions
type Linker() =
member this.GetUri<'T>(action : Expression<Action<'T>>) : string = action.ToString()
type Model() = class end
type Controller() =
member this.Get(s : string) = Model()
let linker = Linker()
let text1 = linker.GetUri<Controller>(fun c -> c.Get("x") |> ignore) // c => op_PipeRight(c.Get("x"), ToFSharpFunc(value => Ignore(value)))
let text2 = linker.GetUri<Controller>(fun c -> ignore(c.Get("x"))) // c => Ignore(c.Get("x"))
printfn "Ok"
UPDATE 2: I've peeked into the source code of Hyprlinkr and I guess I've found the reason.
Current implementation of library code that analyzes expression trees is making certain assumptions about its shape. In particular:
// C#
linker.GetUri((c : Controller) => c.Get("{file-name}"))
- Code assumes that the body of expression tree is method call expression (i.e. invokation of some method from controller)
- Then code picks method call arguments one by one and tries to get its values by wraping them into 0-argument lambda, compiling and running it. Library implicitly relies that argument values are either constant values or values captured from the enclosing environment.
Shape of expression tree generated by F# runtime (i.e. when piping is used) will be
c => op_PipeRight(c.Get("x"), ToFSharpFunc(value => Ignore(value)))
This is still method call expression (so assumption 1 will still be correct) but its first argument uses parameter c. If this argument will be converted to lambda with no arguments (() => c.Get("x")) - then method body of such lambda will refer to some free variable c - precisely what was written in exception message.
As an alternative that will be more F# friendly I can suggest to add extra overload for GetUri
public string GetUri<T, R>(Expression<Func<T, R>> e)
It can be both used on C# and F# sides
// C#
linker.GetUri((Controller c) => c.Get("{filename}"))
// F#
linker.GetUri(fun (c : Controller) -> c.Get("{filename}"))