Reputation: 1380
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
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
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