首页 > 解决方案 > 可以在运行时专门化 F# 函数吗?

问题描述

假设我有一个应该在本地和远程运行的 F# 函数。我想创建一个函数的代理,让那个代理决定在哪里运行函数,这样远程调用对外界是完全不可见的。如果它要在本地运行,代理只会返回函数本身,如下所示:

let proxy_local f = f

如果不是,该函数必须收集其所有参数,然后通过连接发送它们并返回结果。这就是困难的地方:我需要确切地知道用户希望代理哪个函数,还需要知道该函数的参数,这样我就可以在通过网络发送它们之前收集它们。

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

let getMethodInfo = function
    | Call (_, mi, _) -> [], mi
    | Lambdas (vs, Call(_, mi, _)) -> List.map (fun (v: Var list) -> (List.head v).Type) vs, mi
    | _ -> failwith "Not a function"

let proxy_remote (f: Expr) =
    let (argTypes, methodInfo) = getMethodInfo f
    … ? // What to return?

let proxy f = if isLocal then proxy_local f else proxy_remote f

getMethodInfo上面的方法不适用于带有元组参数的方法,我们也需要修改proxy_local,但现在让我们把它放在一边。问题是 的返回值proxy_remote。它应该是一个与原始参数相同的函数,但它应该在最后通过网络发送它们。像这样的东西:

let callRemote (method: MethodInfo) a b c … = doHttpConnection()

但是,需要输入参数。这是真正的问题:由于 F# 函数调用在编译时被转换为 FSharpFunc 子类,因此我不知道如何获得可以与反射一起使用的非专业表示。我真正的问题是:F# 函数可以在运行时使用未知类型进行专门化吗?

我可以想到两种方法来解决这个问题。第一个是proxy本身是通用的:

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 ""
…

这当然可以,但它需要程序员提前考虑每个函数的输入数量。更糟糕的是,具有更多参数的函数将适用于所有接受较少参数的代理方法:

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!

另一种方法是为此目的创建 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 ""

它会起作用,但它可能不会像 F# 编译器生成的那样高效,特别是在所有动态转换的情况下。那么,有没有办法在运行时专门化 F# 函数,所以typedefof<call1<_,_>>.MakeGenericType([| a; methodInfo.ReturnType |])可以用直接调用函数来代替?

标签: reflectionfunctional-programmingf#

解决方案


经过大量搜索,似乎没有办法做我想做的事。然而,我确实了解到,其他人也将 FSharpFunc 子类化为一些非常奇怪的用例,所以它并非完全闻所未闻。如果有人有一些见解可以分享,我会留下这个问题。


推荐阅读