Reputation: 860
I am working with F# to develop PowerShell tooling. I am currently running into a block because Async<_>
is a generic type that is not derived from a non-generic type, so I can't request an Async<_>
or Async
as a parameter value - I have to specify the exact generic type parameter.
(For those unfamiliar with the interaction between these two languages, I can write a class in a .NET language such as F#, derive it from a class in the PowerShell library, and give it a specific attribute and when I run PowerShell and import my library, my class is exposed as a command. The command type can't be generic. Properties of the type are exposed as PowerShell parameters.)
As far as I'm aware I can't avoid this by having a generic member on a non-generic type, so ideally I'd have a transformation attribute (for non-PS users, transformation attributes effectively perform type conversion during runtime parameter binding) to turn Async<_>
into Async<obj>
. For the most part, this would work great for me. However, I can't figure out a way to check if a value is Async<_>
, because the check computation :? Async<_>
at compile time ends up as computation :? Async<obj>
, which is not, unfortunately, the same, and returns false when passed Async<int>
.
I ran into a similar issue in C# and was able to leverage the dynamic
keyword after running a reflection test, and making the parameter be of the derived base type System.Threading.Tasks.Task
, e.g.
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;
}
I am willing to do something like this in F# if possible, but:
?
to compile. Specifically, "none of the types 'Async<'a>, string' support the '?' operator". Not sure what I'm doing wrong as the explanations look straightforward and I can't find any other reports of this message or requirements for that operator.The solutions I have tried are:
/// 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
The other thing I can think of is to use reflection entirely - get the async type, call all of the AsyncBuilder methods reflectively to create a computation expression, and then cast it to Async. As I'm fairly new to F# I'm not sure how well I'd be able to piece together a computation expression like that, and either way it seems a lot more complicated than it ought to be. I'm hoping there is some better way to identify the return type of an async computation and/or just box the result without caring what type it actually is.
EDIT After trying something ridiculously complicated using reflection with the AsyncBuilder type I realized I could leverage it a little more simply. Here is my current working solution, but I'm still looking out for any better options.
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|])
Upvotes: 2
Views: 115
Reputation: 5005
This is how I would do it:
let convert (a: Async<_>) =
async {
let! x = a
return box x
}
And at compile time it behaves as you'd expect:
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
Upvotes: 2