如何提供Expression <action <t>&gt;在F#中,当方法有一个返回值?</action <t>

时间:2013-07-17 07:01:40

标签: f# expression hyprlinkr

我正在尝试将一些C#代码转换为F#。具体来说,我正在尝试使用Hyprlinkr将一些代码转换为F#。

C#代码如下所示:

Href = this.linker.GetUri<ImagesController>(c =>
    c.Get("{file-name}")).ToString()

其中GetUri方法定义为

public Uri GetUri<T>(Expression<Action<T>> method);

ImagesController.Get定义为

public HttpResponseMessage Get(string id)

在F#中,我试图这样做:

Href = linker.GetUri<ImagesController>(
    fun c -> c.Get("{file-name}") |> ignore).ToString())

这会编译,但在运行时抛出此异常:

  

System.ArgumentException未被用户代码
处理    的HResult = -2147024809
   Message =类型'System.Void'的表达式不能用于返回类型'Microsoft.FSharp.Core.Unit'
   源= System.Core程序

据我了解,F#表达式是一个返回unit的表达式,但它应该是Expression<Action<T>>,'返回'void

我正在使用F#3.0(我认为 - 我正在使用Visual Studio 2012)。

我该如何解决这个问题?

3 个答案:

答案 0 :(得分:3)

我的猜测是它应该在F#3.1中修复。这是来自VS2013预览

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:无法检查问题中的实际库,所以我会尝试模仿我看到的界面。此代码在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"

更新2:我已经深入了解了Hyprlinkr的源代码,我想我找到了原因。 分析表达式树的库代码的当前实现正在对其形状做出某些假设。特别是:

// C#
linker.GetUri((c : Controller) => c.Get("{file-name}"))
  1. 代码假定表达式树的主体是方法调用表达式(即从控制器调用某些方法)
  2. 然后代码逐个选择方法调用参数并尝试通过将它们包装成0参数lambda来获取其值,编译并运行它。库隐式地依赖于参数值是常量值或从封闭环境捕获的值。
  3. 由F#运行时生成的表达式树的形状(即使用管道时)将是

    c =&gt; op_PipeRight(c.Get(“x”),ToFSharpFunc(value =&gt; Ignore(value)))

    这仍然是方法调用表达式(假设1仍然是正确的)但它的第一个参数使用参数c。如果这个参数将被转换为没有参数的lambda(()=&gt; c.Get(“x”)) - 那么这个lambda的方法体将引用一些自由变量c - 正是在异常消息中写的内容。 / p>

    作为一种更友好的F#,我可以建议为GetUri添加额外的重载

    public string GetUri<T, R>(Expression<Func<T, R>> e)
    

    它可以在C#和F#侧使用

    // C#
    linker.GetUri((Controller c) => c.Get("{filename}"))
    
    // F#
    linker.GetUri(fun (c : Controller) -> c.Get("{filename}"))
    

答案 1 :(得分:1)

作为F#2.0的变通方法,您可以使用泛型返回类型定义自己的“ignore”函数。这显然允许推断void

let noop _ = Unchecked.defaultof<_>

Href = linker.GetUri<ImagesController>(fun c -> 
    c.Get("{file-name}") |> noop).ToString())

答案 2 :(得分:1)

在这种情况下,我认为您可以在不使用管道的情况下调用ignore

Href = linker.GetUri<ImagesController>(
    fun c -> ignore(c.Get("{file-name}"))).ToString()

<强>更新

鉴于desco对HyprLinkr行为的诊断,似乎你应该能够沿着这些方向使用实用程序:

open System
open System.Linq.Expressions

type ActionHelper =
    static member IgnoreResult(e:Expression<Converter<'t,_>>) = 
        Expression.Lambda<Action<'t>>(e.Body, e.Parameters) 

然后你可以做

Href = linker.GetUri<ImagesController>(
    ActionHelper.IgnoreResult(fun c -> c.Get("{file-name}"))).ToString()