F# - 如何根据返回类型动态创建异步函数

时间:2018-05-02 10:01:33

标签: reflection f# metaprogramming

我正在尝试动态创建一个函数,它可以根据它在F#中的输入返回不同的类型。这些类型的函数就像代理一样,用来说明我想要做的事情,下面是一个 not 正常工作的例子:

open FSharp.Reflection
open System

let functionThatReturnsAsync (irrelevantArgs: obj list) (returnType: Type) = 
    if returnType.GUID = typeof<string>.GUID
    then async { return box "some text" } 
    elif returnType.GUID = typeof<int>.GUID
    then async { return box 42 }
    elif returnType.GUID = typeof<bool>.GUID
    then async { return box true }
    else async { return box null }

// this works fine
let func = FSharpValue.MakeFunction(typeof<string -> Async<int>>, fun x -> box (functionThatReturnsAsync [x] typeof<int>))

// unboxing to that type works as well
let fn = unbox<string -> Async<int>> func 

async {
    // HERE THE ERROR
    let! output = fn "hello"
    printfn "%d" output
}
|> Async.StartImmediate

当我调用fn时,它似乎正在尝试将FSharpFunc<string, FSharpAsync<obj>>投射到FSharpFunc<string, FSharpAsync<int>>,但投射无效。即使没有async CE,只需调用fn来获取异步值失败:

System.InvalidCastException: Specified cast is not valid.
  at (wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
  at Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions.UnboxGeneric[T] (System.Object source) [0x00018] in<5ac785a3dff9fae1a7450383a385c75a>:0
  at <StartupCode$FSharp-Core>.$Reflect+Invoke@820-4[T1,T2].Invoke (T1 inp) [0x00011] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at FSI_0019+it@182-10.Invoke (Microsoft.FSharp.Core.Unit unitVar) [0x0000a] in <a19bbccfdeb3402381709b6f2e8ef105>:0
  at Microsoft.FSharp.Control.AsyncBuilderImpl+callA@522[b,a].Invoke (Microsoft.FSharp.Control.AsyncParams`1[T] args) [0x00051] in <5ac785a3dff9fae1a7450383a385c75a>:0
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <9bbab8f8a2a246e98480e70b0839fd67>:0
  at <StartupCode$FSharp-Core>.$Control+StartImmediate@1223-1.Invoke (System.Runtime.ExceptionServices.ExceptionDispatchInfo edi) [0x00000] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.CancellationTokenOps+StartWithContinuations@964-1.Invoke (System.Runtime.ExceptionServices.ExceptionDispatchInfo x) [0x00000] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.AsyncBuilderImpl+callA@522[b,a].Invoke (Microsoft.FSharp.Control.AsyncParams`1[T] args) [0x00103] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.AsyncBuilderImpl+startAsync@430[a].Invoke (Microsoft.FSharp.Core.Unit unitVar0) [0x00033] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at <StartupCode$FSharp-Core>.$Control.loop@124-50 (Microsoft.FSharp.Control.Trampoline this, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] action) [0x00000] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.Trampoline.ExecuteAction (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] firstAction) [0x00017] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.TrampolineHolder.Protect (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] firstAction) [0x00031] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.AsyncBuilderImpl.startAsync[a] (System.Threading.CancellationToken cancellationToken, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] cont, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] econt, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] ccont, Microsoft.FSharp.Control.FSharpAsync`1[T] p) [0x00013] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.CancellationTokenOps.StartWithContinuations[T] (System.Threading.CancellationToken token, Microsoft.FSharp.Control.FSharpAsync`1[T] a, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] cont, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] econt, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] ccont) [0x00014] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at Microsoft.FSharp.Control.FSharpAsync.StartImmediate (Microsoft.FSharp.Control.FSharpAsync`1[T] computation, Microsoft.FSharp.Core.FSharpOption`1[T] cancellationToken) [0x0002b] in <5ac785a3dff9fae1a7450383a385c75a>:0
  at <StartupCode$FSI_0019>.$FSI_0019.main@ () [0x00019] in <a19bbccfdeb3402381709b6f2e8ef105>:0
  at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <9bbab8f8a2a246e98480e70b0839fd67>:0
Stopped due to error

这是否可行并使示例有效?我不介意甚至摆弄IL发射以使其工作,但我不确定如何。如果对问题不清楚,请告诉我,我会更新。

2 个答案:

答案 0 :(得分:7)

如果你能像Aaron所说的那样利用泛型,那么这样做会更好。但是,如果您需要在运行时选择类型,则可以通过将functionThatReturnsAsync更改为如下所示来使代码正常工作:

let functionThatReturnsAsync (irrelevantArgs: obj list) (returnType: Type) = 
    if returnType.GUID = typeof<string>.GUID
    then box (async { return "some text" })
    elif returnType.GUID = typeof<int>.GUID
    then box (async { return 42 })
    elif returnType.GUID = typeof<bool>.GUID
    then box (async { return true })
    else box (async { return (null:obj) })

这与你所拥有的几乎相同 - 但不是将值置于异步计算中,而是装箱整个异步计算(然后返回正确类型的值) - 所以铸造工程!

答案 1 :(得分:5)

我会通过利用泛型而不是尝试动态创建函数来做到这一点。这是您的代码,经过修改后可以利用泛型类型:

open FSharp.Reflection
open System

let functionThatReturnsAsync<'a> (irrelevantArgs: obj list) = 
    match Unchecked.defaultof<'a> |> box with
    | :? Guid -> async { return box "some text" } 
    | :? Int32 -> async { return box 42 }
    | :? Boolean -> async { return box true }
    | _ -> async { return box null }


// unboxing to that type works as well
let fn<'a> input = 
    async {    
        let! result = functionThatReturnsAsync<'a> [input |> box]
        return result |> unbox<'a>
    }

// This works now
async {
    let! output = fn<int> "hello"
    printfn "%d" output
}
|> Async.RunSynchronously