【问题标题】:F# - How to create an async function dynamically based on return typeF# - 如何根据返回类型动态创建异步函数
【发布时间】:2018-10-12 09:38:48
【问题描述】:

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

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&lt;string, FSharpAsync&lt;obj&gt;&gt; 转换为FSharpFunc&lt;string, FSharpAsync&lt;int&gt;&gt;,但转换无效。即使没有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 发射以使其正常工作,但我不确定如何。如果问题不清楚,请告诉我,我会更新。

【问题讨论】:

    标签: reflection f# metaprogramming


    【解决方案1】:

    如果您可以像 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) })
    

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

    【讨论】:

    • 问题是,异步中的返回值总是装箱为 obj,我从 JSON 中获取值并动态反序列化为正确的returnType,所以我认为正因为如此,即使是装箱整个异步并没有解决问题。如果有一个非通用的unbox(t: Type, o: obj) 我可以使用但我在任何地方都找不到。
    • @ZaidAjaj 有点难以想象具体的场景是什么——你能用代码发布一个新问题来演示这个答案没有解决的方面吗?
    • 我试图动态创建一个记录作为代理对象,每个字段都是函数,根据输入和输出类型提供每个函数的实现。该实现是一个 POST 请求,它将 JSON 结果反序列化为相应函数的正确返回类型。使用反射似乎太难了,需要在运行时进行太多的修改才能让它工作,我求助于引用并得到了一个优雅的解决方案,我会在发布代码时发布结果(代码之前/之后),因为它在一些提交历史,非常感谢您的帮助!
    【解决方案2】:

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

    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
    

    【讨论】:

    • 我认为我的示例过于简化了我原来的问题,您看到的所有函数类型都只能在运行时使用,所以 result |&gt; unbox&lt;'a&gt; 不起作用,我正在寻找一个非通用版本它但没有找到任何
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 1970-01-01
    • 2018-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多