1ambda
1ambda

Reputation: 1155

Can't overload apply method in Scala Implicit class

I am writing a retry function with async and await

  def awaitRetry[T](times: Int)(block: => Future[T]): Future[T] = async {
    var i = 0
    var result: Try[T] = Failure(new RuntimeException("failure"))

    while (result.isFailure && i < times) {
      result = await { Try(block) } // can't compile
      i += 1
    }

    result.get
  }

The Scala compiler reports an error. Since Try doesn't have apply methods takes Future[T]. So I solved it using implicit classes

  implicit class TryCompanionOps(val t: Try.type) extends AnyVal {
    // can't use `apply`!
    def convertTriedFuture[T](f: => Future[T]): Future[Try[T]] = 
      f.map(value => Try(value))
  }  

  // now we can convert Future[T] into Future[Try[T]] e.g,
  await { Try.convertTriedFuture(block) }

My question is,
Why can't I use the name apply instead of convertTriedFuture? It seems that the scala compiler doesn't allow overload only about apply methods in implicit classes.

Thanks.

Upvotes: 0

Views: 394

Answers (1)

Kolmar
Kolmar

Reputation: 14224

Scala starts to look for implicit conversions, only when it can't find an existing method with the required signature. But in Try companion object there is already a suitable method: def apply[T](r: ⇒ T): Try[T], so Scala infers T in apply as Future[Something] and doesn't check for implicit conversions.

Also, this implementation won't work:

def convertTriedFuture[T](f: => Future[T]): Future[Try[T]] = 
  f.map(value => Try(value))

If the Future is failed, map's function isn't called, and the Exception is thrown from await, and async immediately results in a failed Future. So with this implementation the function doesn't actually retry.

You need something like:

def convertTriedFuture[T](f: => Future[T]): Future[Try[T]] =
  f.map(Success.apply).recover { case e => Failure(e) }

Also, I think, it might be cleaner to define this recovery method on Futures:

implicit class FutureAdditionalOps[T](f: Future[T]) {
  def recoverError: Future[Try[T]] =
    f.map(Success.apply).recover { case e => Failure(e) }
}

And then you can have

result = await { block.recoverError }

Upvotes: 2

Related Questions