代码之家  ›  专栏  ›  技术社区  ›  Arshia001

F#函数能在运行时被专门化吗?

  •  0
  • Arshia001  · 技术社区  · 5 年前

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

    let proxy_local f = f
    

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

    好吧,我不能检查函数参数本身,因为在运行时它将是 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#函数调用在 编译时 ,据我所知,没有办法得到一个能与反射一起工作的非专业化表示。我真正的问题是: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 |]) 可以替换为直接调用函数吗?

    0 回复  |  直到 5 年前
        1
  •  1
  •   Arshia001    5 年前

    在到处搜寻之后,似乎没有办法做我想做的事。然而,我确实了解到,其他人也在为一些非常奇怪的用例对FSharpFunc进行子类化,所以这并非完全闻所未闻。我把这个问题留待讨论,以防有人有什么见解可以分享。

        2
  •  1
  •   dvitel    5 年前

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

    type MyF<'U, 'V>(consumeParam: obj -> obj option, cont: 'V) = 
        inherit FSharpFunc<'U, 'V>()
        override __.Invoke(v: 'U) =
            match consumeParam (v :> obj) with 
            | None -> cont
            | Some result -> result :?> 'V
    
    let netCall (outputT: Type) (paramObjs: obj[]): obj = 
        //tcp http etc
        printfn "%A" paramObjs
        Activator.CreateInstance(outputT)
    
    let proxy (f: 't) : 't = 
        let rec collectTypes (t: Type) (acc: Type list) = 
            if t.Name = "FSharpFunc`2" then 
                let args = t.GetGenericArguments()
                collectTypes args.[1] (args.[0]::acc)
            else t::acc           
        let tps = collectTypes (typeof<'t>) [] //here we collected all types from sugnature to array
        printfn "%A" (tps |> List.map(fun x -> x.Name) |> List.rev) //just for debug
        match tps with 
        | [] -> failwithf "Could not be here" //signature cannot be empty
        | [ _ ] -> f //provided param is not a function
        | outputT::inputT::otherT -> //take last two types: ... -> inputT -> outputT
            let mutable paramIndex = 0 //at each call of FSharpFunc we add param to array
            let paramsHolder: obj[] = Array.zeroCreate (otherT.Length + 1)     
            let consumeParam (paramValue: obj) = 
                paramsHolder.[paramIndex] <- paramValue
                paramIndex <- paramIndex + 1
                if paramIndex = paramsHolder.Length then //if all params given
                    Some(netCall outputT paramsHolder)//network call 
                else None
            let initialFunc = //build initial func inputT -> outputT
                typedefof<MyF<_,_>>.MakeGenericType([| inputT; outputT |])
                    .GetConstructors().[0].Invoke([| consumeParam :> obj; Activator.CreateInstance(outputT) |])
            let rec buildF (func: obj) otherT = //recursivelly build other funcs
                match otherT with 
                | [] -> func 
                | inputT::otherT -> 
                    let newFunc = 
                        typedefof<MyF<_,_>>.MakeGenericType([| inputT; func.GetType().BaseType |])
                            .GetConstructors().[0].Invoke([| consumeParam :> obj; func |])
                    buildF newFunc otherT
            let finalFunc = buildF initialFunc otherT
            finalFunc :?> 't  //final cast
    
    [<EntryPoint>]
    let main args =
        let myTestF1 a b = a + b
        let myTestF1Proxied = proxy myTestF1
        printfn "myTestF1Proxied created"
        let res = myTestF1Proxied 1 2
        let myTestF2 a b c d e = 1.0 + a + b * c - d + (float) e
        let myTestF2Proxied = proxy myTestF2
        printfn "myTestF2Proxied created"
        let res = myTestF2Proxied 1. 2. 3. 4. 5
        // let myTestF3 a b c = a::b::c
        // let myTestF3Proxied = proxy myTestF3
        // printfn "myTestF3Proxied created"
        // let res = myTestF3Proxied "test" "a" []
        printfn "Done"
        0
    

    前两个测试的输出:

    ["Int32"; "Int32"; "Int32"]
    myTestF1Proxied created
    [|1; 2|]
    ["Double"; "Double"; "Double"; "Double"; "Int32"; "Double"]
    myTestF2Proxied created
    [|1.0; 2.0; 3.0; 4.0; 5|]
    

    第三次测试在Activator上崩溃。CreateInstance。这是一个有趣的例子,因为这里代理的函数是 let myTestF3 a b c = a::b::c .它是通用的:“a->”a->'一份清单。因此,这就是如何代理此类函数的问题(假设我们可以使用可空类型约束代理,或者以某种方式使用Option<'a>)。

    使现代化

    以下是改进的代码:

    1) 激活剂。CreateInstance已删除-无法在运行时创建FSharpList

    2) invokeFunc是netCall的存根,但可以在另一个远程端使用。函数递归地将参数应用于泛型函数

    3) MyF被分为两类:MyF和MyFInit——这允许移除激活器。CreateInstance的初始调用

    现在第三次测试成功了。代码:

    type MyF<'U, 'V>(consumeParam, cont: 'V) = 
        inherit FSharpFunc<'U, 'V>()
        override __.Invoke(v: 'U) = consumeParam (v :> obj); cont
    
    type MyFInit<'U, 'V>(consumeParam: obj -> obj) = 
        inherit FSharpFunc<'U, 'V>()
        override __.Invoke(v: 'U) = consumeParam (v :> obj) :?> 'V
    
    let invokeFunc f (outputT: Type) (paramObjs: obj[]) = 
        let rec invoke partiallyAppliedFunc givenParams = 
            match givenParams with
            | p::otherParams ->
                let methodInfo = partiallyAppliedFunc.GetType().GetMethod("Invoke", [| p.GetType() |])
                if isNull methodInfo then 
                    partiallyAppliedFunc //fully applied ?
                else 
                    let newFunc = methodInfo.Invoke(partiallyAppliedFunc, [| p |])
                    invoke newFunc otherParams
            | _ -> 
                partiallyAppliedFunc //params are empty 
        invoke f (paramObjs |> Array.toList)
    
    let netCall f (outputT: Type) (paramObjs: obj[]): obj = 
        //tcp http etc instead of invokeFunc
        printfn "%A" paramObjs
        let res = invokeFunc f outputT paramObjs
        printfn "Res: %A" res
        res
    
    let proxy (f: 't) : 't = 
        let rec collectTypes (t: Type) (acc: Type list) = 
            if t.Name = "FSharpFunc`2" then 
                let args = t.GetGenericArguments()
                collectTypes args.[1] (args.[0]::acc)
            else t::acc           
        let tps = collectTypes (typeof<'t>) [] //here we collected all types from sugnature to array
        printfn "%A" (tps |> List.map(fun x -> x.Name) |> List.rev) //just for debug
        match tps with 
        | [] -> failwithf "Could not be here" //signature cannot be empty
        | [ _ ] -> f //provided param is not a function
        | outputT::inputT::otherT -> //take last two types: ... -> inputT -> outputT
            let mutable paramIndex = 0 //at each call of FSharpFunc we add param to array
            let paramsHolder: obj[] = Array.zeroCreate (otherT.Length + 1)   
            let consumeParamInit (paramValue: obj) = 
                paramsHolder.[paramIndex] <- paramValue
                paramIndex <- paramIndex + 1
                netCall f outputT paramsHolder//network call 
            let consumeParam (paramValue: obj) = 
                paramsHolder.[paramIndex] <- paramValue
                paramIndex <- paramIndex + 1
            let initialFunc = //build initial func inputT -> outputT
                typedefof<MyFInit<_,_>>.MakeGenericType([| inputT; outputT |])
                    .GetConstructors().[0].Invoke([| consumeParamInit :> obj |])
            let rec buildF (func: obj) otherT = //recursivelly build other funcs
                match otherT with 
                | [] -> func 
                | inputT::otherT -> 
                    let newFunc = 
                        typedefof<MyF<_,_>>.MakeGenericType([| inputT; func.GetType().BaseType |])
                            .GetConstructors().[0].Invoke([| consumeParam :> obj; func |])
                    buildF newFunc otherT
            let finalFunc = buildF initialFunc otherT
            finalFunc :?> 't  //final cast
    
    [<EntryPoint>]
    let main args = 
        let myTestF1 a b = a + b
        let myTestF1Proxied = proxy myTestF1
        printfn "myTestF1Proxied created"
        let res = myTestF1Proxied 1 2
        let myTestF2 a b c d e = 1.0 + a + b * c - d + (float) e
        let myTestF2Proxied = proxy myTestF2
        printfn "myTestF2Proxied created"
        let res = myTestF2Proxied 1. 2. 3. 4. 5
        let myTestF3 a b c = a::b.ToString()::c
        let myTestF3Proxied = proxy myTestF3
        printfn "myTestF3Proxied created"
        let res = myTestF3Proxied "test" 1 []
        printfn "Done"
        0
    

    输出:

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