从F#返回FSharpFunc给C#

3
我希望在F#中编写一个函数,将以下类型签名对C#公开:
public static FSharpFunc<FSharpFunc<Unit,Unit>,Unit> foo(Action<Action> f)

在F#中,我尝试写成了以下代码:
let foo (f : Action<Action>) : ((unit -> unit) -> unit) = ...

但是这将产生C#签名:

public static void foo(Action<Action> f, FSharpFunc<Unit,Unit> x)

F#将我的代码等同对待:

let foo (f : Action<Action>) (g : unit -> unit) : unit = ...

当然,这些在F#中是等效的,但在C#中却非常不同。我该怎么做才能得到我想要的C#呢?(F# 2.0.0.0)
作为一个快速解决方法,我将我的F#重写为:
let foo (f : Action<Action>) ((unit -> unit) -> unit)[] = ...

然后我只需在C#中使用Head
3个回答

2
如果你写成let foo x = fun () -> ...,那么F#编译器会对代码进行优化,并将其编译为一个接受两个参数的方法(而不是返回函数的方法,这是你所需要的)。为了得到函数值作为结果,你需要在返回函数之前“做些什么”。
// Will be compiled as method taking Action, FastFunc and returning void
let foo1(x : Action<Action>) : (unit -> unit) -> unit = 
  fun f -> f ()

// Will be compiled as method taking Action and returning FastFunc of FastFunc
let foo2(x : Action<Action>) : ((unit -> unit) -> unit) = 
  ignore ();
  fun f -> f ()

话虽如此,以任何方式将F#函数类型暴露给C#都是一种不好的模式,不应该这样做。当您有一些预计要从C#中使用的F# API时,应将函数公开为委托,以便C#消费者可以自然地使用它们(无需显式地将Action转换为F#函数)。通常,在F#方面编写包装更容易。


1
@Thomas:这不应该是(); fun f -> f()吗?然后它就完美地工作了。我故意暴露本机F#接口,然后在F#中编写转换函数(即上面的内容),以便C#调用F#函数和F#转换。原因是C#现在被认为是遗留代码,并将逐渐转移到F#。 - Neil Mitchell
@NeilMitchell 我本来想写 ignore (),但只写 () 似乎也可以!如果你最终转向 F#,那么我想这没问题(但我会为其他人留下警告)。顺便恭喜你将 C# 部分转移到 F#! - Tomas Petricek
@Thomas:我在想,如果假设使用Haskell预定义模块,id $ fun f -> f() 是最纯粹的写法吗? - Neil Mitchell
@NeilMitchell 我猜写 id <| fun f -> ... 的语法可能比使用 ignore 或分号略好一些。但实际上,任何东西都可以 - 只要编译器不会立即看到包含 funlet - Tomas Petricek

2

两种方法:

  1. 添加一个签名文件,其中包含 val foo : Action<Action> -> ((unit -> unit) -> unit)
  2. 使用名义类型的静态成员,而不是模块中的 let-bound 值。也就是说,static member foo (x:Action<Action>) : ((unit -> unit) -> unit) = ...

0

像这样的东西?

open System 
let foo(x : Action<Action>) : (unit -> unit) -> unit = failwith "..."

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接