Reflection F#函数能否在运行时专用?

Reflection F#函数能否在运行时专用?,reflection,functional-programming,f#,Reflection,Functional Programming,F#,让我们假设我有一个F#函数,应该在本地和远程运行。我想为函数创建一个代理,并让该代理决定在何处运行函数,以便外部世界完全看不到远程调用。如果要在本地运行,代理将只返回函数本身,如下所示: let proxy_local f = f 如果没有,函数必须收集所有参数,然后通过连接发送它们并返回结果。这就是问题的症结所在:我需要确切地知道用户希望代理哪个函数,还需要知道该函数的参数,以便在通过网络发送之前收集这些参数 好吧,我不能检查函数参数本身,因为在运行时它将是FSharpFunc的某个未知子类

让我们假设我有一个F#函数,应该在本地和远程运行。我想为函数创建一个代理,并让该代理决定在何处运行函数,以便外部世界完全看不到远程调用。如果要在本地运行,代理将只返回函数本身,如下所示:

let proxy_local f = f
如果没有,函数必须收集所有参数,然后通过连接发送它们并返回结果。这就是问题的症结所在:我需要确切地知道用户希望代理哪个函数,还需要知道该函数的参数,以便在通过网络发送之前收集这些参数

好吧,我不能检查函数参数本身,因为在运行时它将是
FSharpFunc
的某个未知子类,它收集参数并用它们调用函数。为了解决这个问题,我想我需要使用一个报价(顺便说一句,有没有更好的方法来完成这一部分?)

上面的
getMethodInfo
对于具有元组参数的方法不起作用,我们还需要修改
proxy\u local
,但现在我们暂且不谈。问题是
proxy\u remote
的返回值。它应该是一个具有与原始参数相同的参数的函数,但它应该在末尾通过连线发送这些参数。大概是这样的:

let callRemote (method: MethodInfo) a b c … = doHttpConnection()
但是,参数需要键入。这才是真正的问题:因为F#函数调用在编译时被转换成FSharpFunc子类,所以据我所知,没有办法得到一个可以与反射一起工作的非专用表示。我真正的问题是:F#函数在运行时是否可以用未知类型进行专门化

我可以想出两种方法来解决这个问题。第一种方法是让
代理
本身具有通用性:

let call1<'p, 'res> (method: MethodInfo) (p: 'p) = method.Invoke(null, [| p :> obj |]) :?> 'res
let call2<'p1, 'p2, 'res> (method: MethodInfo) (p1: 'p1) (p2: 'p2) = method.Invoke(null, [| p1 :> obj; p2 :> obj |]) :?> 'res
…

let proxy1 (f: Expr<'a -> 'b>) (s: string) : 'a -> 'b =
    let (types, mi) = getMethodInfo f
    match types with
    | [_] -> call1<'a, 'b> mi
    | _ -> failwith ""

let proxy2 (f: Expr<'a -> 'b -> 'c>) : 'a -> 'b -> 'c =
    let (types, mi) = getMethodInfo f
    match types with
    | [_; _] -> call2<'a, 'b, 'c> mi
    | _ -> failwith ""
…
另一种方法是为此目的创建FSharpFunc的特殊子类:

type call1<'a, 'res>(id, ps) =
    inherit FSharpFunc<'a, 'res>()
    override __.Invoke(x: 'a) = callImpl<'res> id ((x :> obj) :: ps)

type call2<'a, 'b, 'res>(id, ps) =
    inherit FSharpFunc<'a, FSharpFunc<'b, 'res>>()
    override __.Invoke(x) = call1<'b, 'res>(id, x :> obj :: ps) :> obj :?> FSharpFunc<'b, 'res>

… 

let proxy (f: Expr<'a -> 'b>) : 'a -> 'b =
    let (types, methodInfo) = getMethodInfo f
    match types with
    | [a] ->
        let t = typedefof<call1<_,_>>.MakeGenericType([| a; methodInfo.ReturnType |])
        t.GetConstructors().[0].Invoke([|methodInfo; []|]) :?> ('a -> 'b)
    | [a; b] ->
        let t = typedefof<call2<_,_,_>>.MakeGenericType([| a; b; methodInfo.ReturnType |])
        t.GetConstructors().[0].Invoke([|methodInfo; []|]) :?> ('a -> 'b)
    … 
    | _ -> failwith ""

类型call1(id,ps)=
继承FSharpFunc()
重写调用(x:'a)=callImpl(id,ps)=
继承FSharpFunc“b>):“a->”b=
let(类型,方法信息)=getMethodInfo f
将类型与
|[a]>
设t=typedefof.MakeGenericType([| a;methodInfo.ReturnType |])
t、 GetConstructors()[0]。调用([| methodInfo;[]|]):?>('a->'b)
|[a;b]>
设t=typedefof.MakeGenericType([| a;b;methodInfo.ReturnType |])
t、 GetConstructors()[0]。调用([| methodInfo;[]|]):?>('a->'b)
… 
|_u3;->与“”一起失败

这是可行的,但它的性能可能不如F#编译器生成的性能,特别是对于所有动态强制转换。那么,有没有办法在运行时专门化F#函数,所以
typedefof.MakeGenericType([\a;methodInfo.ReturnType |])
可以替换为直接调用函数?

经过大量搜索,似乎没有办法实现我想要的。然而,我确实了解到,其他人也在为一些非常奇怪的用例对FSharpFunc进行子类化,所以这并非完全没有听说过。我把这个问题留着,以防有人有什么见解可以分享。

这不是对你问题的直接回答。但这是我玩过的实现(希望它能有所帮助):

键入MyF(consumerparam:obj->obj选项,cont:'V)=
继承FSharpFunc()
重写_U.Invoke(v:'U)=
将consumerparam(v:>obj)与
|无->继续
|某些结果->结果:?>'V
let netCall(output:Type)(paramObjs:obj[]):obj=
//tcp http等
printfn“%A”参数对象
Activator.CreateInstance(输出)
let代理(f:'t):'t=
让rec收集类型(t:类型)(acc:类型列表)=
如果t.Name=“FSharpFunc`2”,则
设args=t.GetGenericArguments()
collectTypes参数[1](参数[0]::acc)
其他t::acc
让tps=collectTypes(typeof’a list。因此,这是如何代理此类函数的问题(假设我们可以使用可空类型约束代理或使用选项obj):?>'V
让invokeFunc f(output:Type)(paramObjs:obj[])=
让rec调用partiallyAppliedFunc给定参数=
将给定的图形与
|p::其他参数->
让methodInfo=partiallyAppliedFunc.GetType().GetMethod(“Invoke”,[p.GetType()|])
如果是null methodInfo,那么
部分应用功能//完全应用?
其他的
让newFunc=methodInfo.Invoke(partiallyAppliedFunc,[| p |])
调用newFunc其他参数
| _ -> 
partiallyAppliedFunc//参数为空
调用f(paramObjs |>Array.toList)
let netCall f(output:Type)(paramObjs:obj[]):obj=
//tcp http etc而不是invokeFunc
printfn“%A”参数对象
让res=invokeFunc f output paramObjs
printfn“Res:%A”Res
物件
let代理(f:'t):'t=
让rec收集类型(t:类型)(acc:类型列表)=
如果t.Name=“FSharpFunc`2”,则
设args=t.GetGenericArguments()
collectTypes参数[1](参数[0]::acc)
其他t::acc

让tps=collectTypes(TypeOf)我认为第三个测试崩溃的原因是您假设函数的所有输入都是同一类型的,因此当您得到不同的输入(最后一个参数中的列表)时,您的代码就会崩溃。@Arshia001-我在上面发布了一个更新。不,这不是一种情况:输出参数可能是不同类型的(参见输出)。尽管提供的代码可以工作,但在本地为函数调用提供一些修饰是有用的。但是要远程调用函数(我指的是相同的函数),在发送请求之前,初始函数代码应该存在于远程端。当然,我们假设在代码运行的任何地方都上载相同的库。
let f a b = a + b
let fproxy = proxy1 f // The result will be of type int -> (int -> int), not what we want at all!
type call1<'a, 'res>(id, ps) =
    inherit FSharpFunc<'a, 'res>()
    override __.Invoke(x: 'a) = callImpl<'res> id ((x :> obj) :: ps)

type call2<'a, 'b, 'res>(id, ps) =
    inherit FSharpFunc<'a, FSharpFunc<'b, 'res>>()
    override __.Invoke(x) = call1<'b, 'res>(id, x :> obj :: ps) :> obj :?> FSharpFunc<'b, 'res>

… 

let proxy (f: Expr<'a -> 'b>) : 'a -> 'b =
    let (types, methodInfo) = getMethodInfo f
    match types with
    | [a] ->
        let t = typedefof<call1<_,_>>.MakeGenericType([| a; methodInfo.ReturnType |])
        t.GetConstructors().[0].Invoke([|methodInfo; []|]) :?> ('a -> 'b)
    | [a; b] ->
        let t = typedefof<call2<_,_,_>>.MakeGenericType([| a; b; methodInfo.ReturnType |])
        t.GetConstructors().[0].Invoke([|methodInfo; []|]) :?> ('a -> 'b)
    … 
    | _ -> failwith ""

["Int32"; "Int32"; "Int32"]
myTestF1Proxied created
[|1; 2|]
["Double"; "Double"; "Double"; "Double"; "Int32"; "Double"]
myTestF2Proxied created
[|1.0; 2.0; 3.0; 4.0; 5|]
["Int32"; "Int32"; "Int32"]
myTestF1Proxied created
[|1; 2|]
Res: 3
["Double"; "Double"; "Double"; "Double"; "Int32"; "Double"]
myTestF2Proxied created
[|1.0; 2.0; 3.0; 4.0; 5|]
Res: 9.0
["String"; "Int32"; "FSharpList`1"; "FSharpList`1"]
myTestF3Proxied created
[|"test"; 1; []|]
Res: ["test"; "1"]
Done