How can I provide the expression <Action <T>> in F # when the method has a return value?
I am trying to convert C # code to F #. In particular, I am trying to convert some code using Hyprlinkr to F #.
C # code is as follows:
Href = this.linker.GetUri<ImagesController>(c => c.Get("{file-name}")).ToString() where the GetUri method GetUri defined as
public Uri GetUri<T>(Expression<Action<T>> method); and ImagesController.Get is defined as
public HttpResponseMessage Get(string id) In F #, I'm trying to do this:
Href = linker.GetUri<ImagesController>( fun c -> c.Get("{file-name}") |> ignore).ToString()) This compiler, but at runtime throws this exception:
System.ArgumentException is not handled by user code
HResult = -2147024809
Message = Expression of type "System.Void" cannot be used for return type "Microsoft.FSharp.Core.Unit"
Source = System.Core
As far as I understand, the F # expression is an expression that returns unit , but should really be Expression<Action<T>> , 'return' void .
I am using F # 3.0 (I think I am using Visual Studio 2012).
How can I solve this problem?
I assume 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: It is not possible to check with a real library the question, so I will try to imitate the interface that 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 looked at the Hyprlinkr source code, and I think I found the reason. The current implementation of library code that analyzes expression trees makes certain assumptions about its form. In particular:
// C# linker.GetUri((c : Controller) => c.Get("{file-name}")) - The code assumes that the body of the expression tree is an expression of a method call (i.e., a call to some method from the controller).
- Then the code selects the arguments to the method call one by one and tries to get its values, breaking them into the lambda 0 argument, compiling and running it. The library implicitly assumes that argument values are either constant values or values obtained from the environment.
The shape of the expression tree generated at runtime by F # (i.e. when using a pipeline) will be
c => op_PipeRight (c.Get ("x"), ToFSharpFunc (value => Ignore (value)))
This is still an expression of the method call (so assumption 1 will still be correct), but its first argument uses the c parameter. If this argument is converted to lambda without arguments (() => c.Get ("x")) - then the body of such a lambda method will refer to some free variable c - exactly what was written in the exception message.
As an alternative, which will be more F # friendly, I can suggest adding extra overload for GetUri
public string GetUri<T, R>(Expression<Func<T, R>> e) It can be used on both C # and F # sides.
// C# linker.GetUri((Controller c) => c.Get("{filename}")) // F# linker.GetUri(fun (c : Controller) -> c.Get("{filename}")) As a workaround for F # 2.0, you can define your own “ignore” function with a typical return type. This, apparently, allows us to conclude void .
let noop _ = Unchecked.defaultof<_> Href = linker.GetUri<ImagesController>(fun c -> c.Get("{file-name}") |> noop).ToString()) In this case, I think you can just call ignore without using a channel:
Href = linker.GetUri<ImagesController>( fun c -> ignore(c.Get("{file-name}"))).ToString() UPDATE
If you specified a diagnosis of HyprLinkr behavior, it seems to you that you should use the utility in the following lines:
open System open System.Linq.Expressions type ActionHelper = static member IgnoreResult(e:Expression<Converter<'t,_>>) = Expression.Lambda<Action<'t>>(e.Body, e.Parameters) Then you can do
Href = linker.GetUri<ImagesController>( ActionHelper.IgnoreResult(fun c -> c.Get("{file-name}"))).ToString()