Chirlo
Chirlo

Reputation: 6122

Upper and Lower bound on scala type

Consider the following hierarchy:

class C1
class C2 extends C1
class C3 extends C2
class C4 extends C3

I want to write a function that just accepts types C2 and C3. For that I thought of the following:

 def f [C >: C3 <: C2](c :C) = 0

I'd expect the following behaviour

f(new C1)  //doesn't compile, ok
f(new C2)  //compiles, ok
f(new C3)  //compiles, ok
f(new C4)  // !!! Compiles, and it shouldn't 

The problem is when calling it with C4, which I don't want to allow, but the compiler accepts. I understand that C4 <: C2 is correct and that C4 can be seen as a C3. But when specifying the bound [C >: C3 <: C2], I would expect the compiler to find a C that respects both bounds at the same time, not one by one.

Question is : Is there any way to achieve what I want, and if not, is the compiler trying to avoid some inconsistency with this?

Edit: from the answers I realized that my presumption is wrong. C4 always fulfills C >: C3, so both bounds are indeed respected. The way to go for my use case is C3 <:< C.

Upvotes: 4

Views: 704

Answers (2)

I See Voices
I See Voices

Reputation: 842

I found that explanation from another stackoverflow question very helpful:

S >: T simply means that if you pass in a type S that is equal to T or its parent, then S will be used. If you pass a type that is sublevel to T then T will be used.

So in your example all, but first should compile. Following example illustrates the meaning of that: Let's redefine f:

def f[U >: C3 <: C2](c: U) = c

and then:

 val a2 = f(new C2)
 val a3 = f(new C3) 
 val a4 = f(new C4) 
 List[C2](a2, a3, a4)  //compiles
 List[C3](a3, a4)  //compiles
 List[C4](a4)  //does not cause a4 is C3

Hope that helps.

Upvotes: 5

Kolmar
Kolmar

Reputation: 14224

Statically, yes. It's pretty simple to impose this constraint:

def f[C <: C2](c: C)(implicit ev: C3 <:< C) = 0

f(new C4) wouldn't compile now.

The problem is, it's probably not possible to prohibit the following behaviour at compile time:

val c: C3 = new C4
f(c)

Here variable c has static type C3, which passes any kind of typechecking by compiler, but it is actually a C4 at runtime.

At runtime you can of course check the type using reflection or polymorphism and throw errors or return Failure(...) or None

Upvotes: 11

Related Questions