F # - Как создать асинхронную функцию динамически на основе типа возвращаемого значения - PullRequest
0 голосов
/ 02 мая 2018

Я пытаюсь динамически создать функцию, которая может возвращать различные типы на основе ее ввода в 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<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 голосов
/ 02 мая 2018

Если вы можете использовать дженерики, как советует Аарон, тогда лучше сделать это. Однако если вам нужно выбрать тип во время выполнения, вы можете заставить свой код работать, изменив 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) })

Это почти то же самое, что и у вас - но вместо того, чтобы помещать значения в в асинхронные вычисления, оно объединяет в себе все асинхронные вычисления (которые затем возвращают значение правильного типа) - так что литейные работы!

0 голосов
/ 02 мая 2018

Я бы сделал это, используя дженерики, вместо того, чтобы пытаться динамически создавать функцию. Вот ваш код, модифицированный таким образом, чтобы использовать вместо него универсальные типы:

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
...