Robert Elliot
Robert Elliot

Reputation: 1512

Scala - restrict generic type to anything other than Future

I have a couple of methods on a trait as so:

trait ResourceFactory[+R] {

    def using[T](work: R => T): T

    def usingAsync[T](work: R => Future[T]): Future[T]

}

Unfortunately there's nothing in the type checker to stop you calling the first using method with a function returning a Future. I'd like the compiler to insist that the type of T in the first method be anything other than Future, to prevent that mistake - is that possible?

Thanks

Upvotes: 1

Views: 261

Answers (2)

Robert Elliot
Robert Elliot

Reputation: 1512

Here's an alternative I've stolen shamelessly from https://github.com/japgolly/scalajs-react/blob/cb75721e3bbd0033ad63d380bcaddc96fbe906e3/core/src/main/scala/japgolly/scalajs/react/Callback.scala#L21-L31:

@implicitNotFound("You're returning a ${A}, which is asynchronous, which means the resource may be closed before you try and use it. Instead use usingAsync.")
final class NotFuture[A] private[ResourceFactoryTests]()
object NotFuture {
  final class Proof[A] private[ResourceFactory]()
  object Proof {
    implicit def preventFuture1[A]: Proof[Future[A]] = ???
    implicit def preventFuture2[A]: Proof[Future[A]] = ???
    @inline implicit def allowAnythingElse[A]: Proof[A] = null
  }
  @inline implicit def apply[A: Proof]: NotFuture[A] = null
}

which can be used as:

trait ResourceFactory[+R] {
  def using[T: ResourceGuard](work: R => T): T
  def usingAsync[T](work: R => Future[T]): Future[T]
}

This has the advantage of not having to add the implicit arg every time you implement the method, but the disadvantage that I'm pure cargo culting - I don't understand why it works, though it seems to use similar principles to shapeless.

Upvotes: 1

Marth
Marth

Reputation: 24812

You can use shapeless' <:!<:

import scala.concurrent.Future
import shapeless._

trait ResourceFactory[+R] {
  def using[T](work: R => T)(implicit ev: T <:!< Future[_]): T = ???
  def usingAsync[T](work: R => Future[T]): Future[T] = ???
}

Then:

scala> val r = new ResourceFactory[Int] {}           
r: ResourceFactory[Int] = $anon$1@effe6ad            

// Compiles (the error is due to the use of ???)
scala> r.using(_.toString)
scala.NotImplementedError: an implementation is missing                                                           

// Doesn't compile
scala> r.using(Future.successful(_))                 
<console>:17: error: ambiguous implicit values:      
 both method nsubAmbig1 in package shapeless of type [A, B >: A]=> shapeless.<:!<[A,B]                    
 and method nsubAmbig2 in package shapeless of type [A, B >: A]=> shapeless.<:!<[A,B]                     
 match expected type shapeless.<:!<[scala.concurrent.Future[Int],scala.concurrent.Future[_]]              
       r.using(Future.successful(_))   

Upvotes: 3

Related Questions