【问题标题】:F# Async<_> to Async<obj>F# Async<_> 到 Async<obj>
【发布时间】:2021-12-31 05:23:55
【问题描述】:

我正在使用 F# 开发 PowerShell 工具。我目前遇到了一个障碍,因为Async&lt;_&gt; 是一个泛型类型,它不是从非泛型类型派生的,所以我不能请求Async&lt;_&gt;Async 作为参数值 - 我必须指定确切的泛型类型参数。

(对于那些不熟悉这两种语言之间交互的人,我可以用 .NET 语言(例如 F#)编写一个类,从 PowerShell 库中的一个类派生它,并给它一个特定的属性,当我运行 PowerShell并导入我的库,我的类作为命令公开。命令类型不能是通用的。类型的属性作为 PowerShell 参数公开。)

据我所知,我无法通过在非泛型类型上使用泛型成员来避免这种情况,所以理想情况下我会有一个转换属性(对于非 PS 用户,转换属性有效地执行类型转换在运行时参数绑定期间)将Async&lt;_&gt; 转换为Async&lt;obj&gt;。在大多数情况下,这对我很有用。但是,我无法找到检查值是否为 Async&lt;_&gt; 的方法,因为在编译时检查 computation :? Async&lt;_&gt; 最终结果为 computation :? Async&lt;obj&gt;,这 不是,不幸的是,同样,通过Async&lt;int&gt;时返回false。

我在 C# 中遇到了类似的问题,并且在运行反射测试后能够利用 dynamic 关键字,并使参数成为派生的基本类型 System.Threading.Tasks.Task,例如

const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHeirarchy;
var isTaskOf = task.GetType()
    .GetProperty("GetAwaiter", flags)
    .PropertyType
    .GetMethod("GetResult", flags)
    .ReturnType != typeof(void);
if (isTaskOf) {
  var result = await (dynamic)task;
}

如果可能的话,我愿意在 F# 中做这样的事情,但是:

  • 我无法成功获取dynamic lookup operator ? 进行编译。具体来说,“'Async, string' 类型都不支持 '?'操作员”。不知道我做错了什么,因为解释看起来很简单,而且我找不到关于此消息的任何其他报告或该操作员的要求。
  • 我不知道这是否可行,或者该运算符是否仅用于动态访问对象的成员。

我尝试过的解决方案是:

/// Transform from Async<_> to Async<obj>
override _.Transform(_, item : obj) : obj =
  match item with
  // only matches Async<obj>. I get a compiler warning that _ is constrained to obj
  | :? Async<_> as computation ->
    let boxedComputation : Async<obj> = async { return! computation }
    boxedComputation
  // if the value is not an async computation, let it pass through. This will allow other transformation or type converters to try to convert the value
  | _ -> item

override _.Transform(_, item) =
  // no compiler warning about the type being constrained to obj, but the if test does not pass unless item is Async<obj>
  if (item :? Async<_>) then async { return! item :?> Async<_> }
  else item

我能想到的另一件事是完全使用反射 - 获取异步类型,反射调用所有 AsyncBuilder 方法以创建计算表达式,然后将其转换为异步。由于我对 F# 还很陌生,所以我不确定我能够将这样的计算表达式拼凑起来有多好,而且无论哪种方式,它似乎都比它应该的要复杂得多。我希望有更好的方法来识别异步计算的返回类型和/或只是将结果装箱而不关心它实际上是什么类型。


编辑 在使用 AsyncBuilder 类型的反射尝试了一些非常复杂的事情后,我意识到我可以更简单地利用它。这是我目前的工作解决方案,但我仍在寻找更好的选择。


    static let boxAsyncReturnValue v = async { return v :> obj }
    static let bindFunctionReflected = typeof<FSharpAsyncObjTransformationAttribute>.GetMethod(
        nameof boxAsyncReturnValue,
        BindingFlags.NonPublic ||| BindingFlags.Static
        )

    override _.Transform(engineIntrinsics, item) =
        // I need to identify the current return type of the computation, and quit if "item" is not Async<_>
        if item = null then item else
        let itemType = item.GetType()
        if not itemType.IsGenericType then item else
        let genericItemType = itemType.GetGenericTypeDefinition()
        if genericItemType <> typedefof<Async<_>> then item else
        let returnType = itemType.GetGenericArguments()[0]
        if returnType = typeof<obj> then item else
        bindFunctionReflected.MakeGenericMethod(itemType).Invoke(null, [|item|])

【问题讨论】:

    标签: powershell generics f#


    【解决方案1】:

    我会这样做:

    let convert (a: Async<_>) =
        async {
            let! x = a
            return box x
        }
    

    并且在编译时它的行为与您期望的一样:

    let a = async { return "hello" }
    
    let o: Async<obj> = convert a
    
    let res = Async.RunSynchronously o
    
    printfn "%s" res // Error: expected type 'string' but is type 'obj'
    printfn "%s" (unbox<string> res) // compiles, prints the string
    

    【讨论】:

    • 不幸的是,据我所知,这对我不起作用,因为我无法调用convert,因为我得到的item 参数是obj 类型的,我可以' t 将其转换为兼容的类型以供转换函数使用。
    • 我不明白评论。听起来你的问题不是如何从Async&lt;_&gt;Async&lt;obj&gt;,而是obj 到别的东西?
    • 我得到obj,如果objAsync&lt;_&gt; 的一个实例,我想将其转换为Async&lt;obj&gt;
    猜你喜欢
    • 2017-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多