Michael Pollmeier
Michael Pollmeier

Reputation: 1380

Scala: constrain a class type parameter for a member function

Given a class A with a type parameter T, how can I further constrain T for a specific function? In the example below, I want to enforce that foo can only be called if T is an Int (or a subtype of, if you like). I.e. new A[Int]().foo() should compile, but new A[Double]().foo() should not compile.

I can achieve what I want with an implicit conversion T => Int as demonstrated in foo. However that results in an unnecessary method invocation - the evidence that is implicitly found is the identity function. Also, it doesn't look nice...

class A[T] {
  val someValue: T = ???

  def foo()(implicit ev: T => Int): Int = ev(someValue)

  // some ideas that don't quite work
  // def bar[T2 <: Int :EqualTo[T]](t2: T2): T = t2.asInstanceOf[T]
  // def bam[T2 <: T with Int](): Int = someValue.asInstanceOf[Int]

  def thisOneDefinesItsOwnConstraintType[Z <: Int](z: Z): Z = z
  def thisOneDoesNotNeedToConstrainT(t: T): T = someValue
}

The solution gets bonus points if it does not need to cast :)

Note: I know that I can define a type constraint either on the class or method level (see thisOneDefinesItsOwnConstraintType), but that doesn't help me because I also have some methods that don't need to constrain the type (see thisOneDoesNotNeedToConstrainT).

Upvotes: 1

Views: 1025

Answers (2)

Michael Zajac
Michael Zajac

Reputation: 55569

I don't see a way to do this without implicits, because while you can put some constraint B <: C on a class method, your requirement that B <: T is difficult to generalize, because we have no idea how C relates to an arbitary T. Fortunately, Scala has a construct for this already, the <:< type-class. An instance of <:<[B, T] generated in Predef will provide evidence that B <: C.

But don't just take my word for it, here's what the scaladoc has to say:

To constrain any abstract type T that's in scope in a method's argument list (not just the method's own type parameters) simply add an implicit argument of type T <:< U, where U is the required upper bound; or for lower-bounds, use: L <:< T, where L is the required lower bound.

For example:

class E
class F extends E
class G extends F

class A[T] {
  def constrained[B <: F](b: B)(implicit ev: B <:< T): B = b
}

Here, we introduce an upper-bound of F for type parameter B of the contrained method. But if you also want to guarantee that B <: T (without adding another type parameter), you can add the implicit B <:< T.

scala> val a = new A[E]
a: A[E] = A@5a63f509

scala> a.constrained(new E)
<console>:15: error: inferred type arguments [E] do not conform to method constrained's type parameter bounds [B <: F]
       a.constrained(new E)
         ^
<console>:15: error: type mismatch;
 found   : E
 required: B
       a.constrained(new E)
                     ^

scala> a.constrained(new F)
res5: F = F@4a668b6e

scala> a.constrained(new G)
res6: G = G@4de4b452

Upvotes: 5

Archeg
Archeg

Reputation: 8462

How about:

implicit class AIntOps(a: A[Int]) {
  def test(x: Int) = x
}

With that method test will only appear for A[Int] and simply not available for any other A

Upvotes: 1

Related Questions