fsprojects / FSharp.Control.AsyncSeq

Asynchronous sequences for F#
https://fsprojects.github.io/FSharp.Control.AsyncSeq/
Other
161 stars 59 forks source link

Best way to convert System.Collections.Generic.IAsyncEnumerable<'t> to AsyncSeq<'t>? #119

Closed fwaris closed 3 years ago

fwaris commented 3 years ago

The Azure EventHub API returns a IAsyncEnumberable<'t> (from BCL).

I would like to convert it to asyncSeq (or FSharp.Control.IAsyncEnumerable<'t>) (for obvious reasons).

The following code type checks but there might be a better way that I am missing. Any suggestions would be appreciated.

    let toAsyncSeq<'t> (xs:System.Collections.Generic.IAsyncEnumerable<'t>) =
        let aeEnum = xs.GetAsyncEnumerator()
        let rec loop() = 
            asyncSeq {
                let! haveData = aeEnum.MoveNextAsync().AsTask() |> Async.AwaitTask
                if haveData then 
                    yield aeEnum.Current
                    yield! loop()
            }
        loop()
fwaris commented 3 years ago

This works (the first version does not as it unallocates aeEnum). This also properly handles disposition.

    let toAsyncSeq<'t> (xs:System.Collections.Generic.IAsyncEnumerable<'t>) =
        {new FSharp.Control.IAsyncEnumerable<'t> with
            member _.GetEnumerator() =        
                let aeEnum = xs.GetAsyncEnumerator()
                {
                    new FSharp.Control.IAsyncEnumerator<'t> with 
                        member _.MoveNext() = 
                            async {
                                let! haveData = aeEnum.MoveNextAsync().AsTask() |> Async.AwaitTask
                                if haveData then return Some aeEnum.Current else return None
                            }

                    interface IDisposable with
                        member _.Dispose() = 
                            aeEnum.DisposeAsync().AsTask() 
                            |> Async.AwaitTask                            
                            |> Async.RunSynchronously
                }
        }
fwaris commented 3 years ago

I did not realize this before but the .net core version of AsyncSeq does have a function 'AsyncSeq.ofAsyncEnum' to convert from BCL IAsyncEnumberable<'t> to the FSharp.Control.IAsyncEnumberable<'t>.

The .net framework version of the package does not. I was using the .net framework version earlier as I also wanted to use fsharp interactive with EventHubs and therefore had to write my own conversion. FSI is based on .net framework in VS 2019 at least as of now.

wilsoncg commented 3 years ago

@fwaris

FSI is based on .net framework in VS 2019

Yes this one has caught me out a few times now. A lot of F# developers in the community are using vscode as their preferred environment, development on the tooling seems to iterate just a tad quicker.

Also, you could try dotnet fsi with .NET 5.0 installed, that should give you .NET core?

fwaris commented 3 years ago

@wilsoncg I also use vscode but intellisense does not work well for .fsx files in vscode.

Intellisense works fine for files inside a project.

Also I am now using #r "nuget: ...." refs in .fsx files and I don't see them working in vscode even with the 'languagepreview' flag set.

wilsoncg commented 3 years ago

@fwaris Ok, I've just tried out and it works in vscode with ionide 4.17.0.

You should now see syntax highlighting play nicely with #r nuget syntax, using ionide & FSAC.

There is a new build of ionide 5.0.0 coming very soon, which will use .NET 5.0 by default & should mean --langversion is not needed.

image

fwaris commented 3 years ago

@wilsoncg appreciate your help. Indeed the above works however adding a moderate amount of complexity to the script stops intellisense from working in vscode - please see below:

let torchlib = @"C:\Users\Admin\.nuget\packages\libtorch-cpu\1.7.0.1\runtimes\win-x64\native\torch_cpu.dll"
let _ = System.Runtime.InteropServices.NativeLibrary.Load torchlib

#r "nuget: DiffSharp-cpu,1.0.0-preview-387146713"
#r "nuget:XPlot.Plotly"
open XPlot.Plotly

let stacks = {|
    Name = "Name"
    Hours = [23;22;12]
|}
open DiffSharp

The code works fine when submitted to fsi so it is correct code (Note: you will have to set the correct path to the referenced native library, for your own environment)