Kevin Meredith
Kevin Meredith

Reputation: 41939

Type Parameters in `abstract class`

Given:

scala> abstract class Foo[A] {
     |   def f: A
     | }
defined class Foo

, and an implementation

scala> class FooImpl extends Foo[Any] {
     |   def f: Any = "foo"
     | }
defined class FooImpl

and then an instantiation

scala> new FooImpl().f
res0: Any = foo

res0 has type Any, as I'd expect since FooImpl uses Any as its type parameter to Foo.

And, given a second implementation:

scala> class FooImpl2 extends Foo[Any] {
     |   override def f= "foo"
     | }
defined class FooImpl2

and an instantiation and call to f:

scala> new FooImpl2().f
res1: String = foo

Why is res1's type a String?

Whereas, given:

scala> def g[A](x: A):A = x
g: [A](x: A)A

I can pass y, an Int:

scala> val y: Int = 55
y: Int = 55

to g

scala> g[AnyVal](y)
res3: AnyVal = 55

and get back an AnyVal.

Finally,

scala> g(55)
res5: Int = 55

returns an Int, as expected.

However, I would've expected FooImpl2#f to have returned an Any given FooImpl2's extends Foo[Any].

Why not?

Upvotes: 0

Views: 875

Answers (3)

Jasper-M
Jasper-M

Reputation: 15086

A subclass can narrow the return types of the methods that it overrides.
The return type of f in Foo[Any] is Any. FooImpl2 is a subtype of Foo[Any] and you didn't specify fs return type so the compiler infers that it is String, which is a subtype of Any and thus satisfies all constraints.

Upvotes: 1

Michael Zajac
Michael Zajac

Reputation: 55569

When you override a member of an abstract class or trait, you are allowed to narrow its type to something more specific. It doesn't appear that way here, because you're relying on type inference for override def f = "foo", but it's really override def f: String = "foo".

This is legal:

abstract class Foo[A] {
  def f: A
}

class FooImpl2 extends Foo[Any] {
  override def f: String = "foo"
}

String still conforms to the type parameter A = Any, but f has been refined in FooImpl2 to String.

Here is an example without the type parameter:

abstract class A { def f: Any }
class B extends A { override def f: String = "a" }

g[AnyVal](y) is a just very different example. Since you are manually supplying the type parameter of AnyVal to g, you are asking the compiler to make sure y is AnyVal, but it doesn't matter if it is some more specific type (a more specific type returned by method will always be up-cast to the return type). In FooImpl2 you are simply changing the signature of f.

Upvotes: 7

Łukasz
Łukasz

Reputation: 8673

When you override a method, you can make it return more specific type. In this line

override def f= "foo"

you didn't specify return type and it was inferred to be String

Upvotes: 4

Related Questions