abatishchev
abatishchev

Reputation: 100238

How to implement interface having method returning Task (non-generic) in F#

Say I have interface:

interface IProductRepository
{
    Task SaveProduct(Product p);
}

previously implemented by C# class:

class CSharpProductRepository : IProductRepository
{
    public Task SaveProduct(Product p)
    {
        _db.Products.Add(p);
        return _db.SaveChangesAsync();
    }
}

Now I want to implement the same in F#:

type FSharpProductRepository =
    interface IProductRepository with
        member this.SaveProduct(p : Product) : Task = this.SaveProduct(p) // error 1

    member this.SaveProduct(p : Product) = async {
        db.Products.Add(p)
        return db.SaveChangesAsync() |> Async.AwaitTask // error 2
    }

But getting an error (1):

This expression was expected to have type Task but here has type Async<'a>

and (2):

Type constraint mismatch. The type Task is not compatible with type Task<'a>

Upvotes: 3

Views: 816

Answers (2)

desco
desco

Reputation: 16782

Given that Task<'a> is a subtype of Task you can do smth like this:

open System.Threading.Tasks

// stubs since I don't know what the actual code looks like
type Product = class end

type IProductRepository = 
    abstract SaveProduct: product: Product -> Task

type Db = 
    abstract Products: System.Collections.Generic.ICollection<Product>
    abstract SaveProductAsync: product: Product -> Task<int>

type Repository(db: Db) = 
    interface IProductRepository with
        member this.SaveProduct(p: Product) = 
            db.Products.Add(p)
            upcast db.SaveProductAsync(p)

Upvotes: 6

Random Dev
Random Dev

Reputation: 52270

you need to start the async workflow with Async.StartAsTask but sadly that does not work here as this will need a generic task Task<'a> :( - so I think you have to use the AwaitWaitHandle method with the tasks AsyncWaitHanlde

type FSharpProductRepository =
    interface IProductRepository with
        member this.SaveProduct(p : Product) : Task = 
           this.SaveProduct(p)

    member this.SaveProduct(p : Product) = 
       async {
          db.Product.Add(p)
          let task = db.SaveChangesAsync() 
          let! _ = Async.AwaitWaitHandle (task :> IAsyncResult).AsyncWaitHandle
          return ()
       } |> Async.StartAsTask :> _

return the task directly:

It makes more sense to return the task directly, as in the code above (that just mirrors the C# implementation) it just starts a thread, that starts another (db.SaveChanges()) waits for the changes to be saved and returns ... seems overkill to me.

This only would IMO only make sense if you would continue to work with the async-Workflow (remove the Async.StartAsTask - or if you would use the overload of AwaitWaitHandle that will timeout after some milliseconds).

    member this.SaveProduct(p : Product) = 
      db.Product.Add(p)
      db.SaveChangesAsync()

Upvotes: 4

Related Questions