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?

+7
expression f # hyprlinkr
source share
3 answers

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}")) 
+3
source share

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()) 
+1
source share

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() 
+1
source share

All Articles