ptrlaszlo
ptrlaszlo

Reputation: 337

Is there a reason to use subtype as type parameter in Scala?

I was wondering whether there is a good reason to use subtype as functions type parameter? Lets consider the following example:

scala> trait Animal { def sound: String }
defined trait Animal

scala> def f1[T <: Animal](a: T) = a.sound
f1: [T <: Animal](a: T)String

scala> def f2(a: Animal) = a.sound
f2: (a: Animal)String

Has f1 some advantages over f2?

Upvotes: 6

Views: 114

Answers (4)

dkolmakov
dkolmakov

Reputation: 667

Considering your example where f1 and f2 have the same output type the f2 function has an advantage before f1 if you want to overload your method.

So for f1 it will give an error:

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Zoo {
  def f1[T <: Animal](a: T) = a.sound
  def f1[T <: Dog](a: T) = "Dog says " + a.sound
}

// Exiting paste mode, now interpreting.

<console>:18: error: method f1 is defined twice
  conflicting symbols both originated in file '<console>'
         def f1[T <: Dog](a: T) = "Dog says " + a.sound
             ^

while with f2 it works:

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Zoo {
  def f2(a: Animal) = a.sound
  def f2(a: Dog) = "Dog says " + a.sound
}

// Exiting paste mode, now interpreting.

defined class Zoo

Upvotes: 1

drhr
drhr

Reputation: 2281

Both functions f1 and f2 are very similar. If you output the bytecode, you'll see up in the constants pool that they are denoted as:

#71 = Methodref          #12.#70       // Main$.f2:(LMain$Animal;)Ljava/lang/String;
#74 = Methodref          #12.#73       // Main$.f1:(LMain$Animal;)Ljava/lang/String;

As far as the bytecode is concerned, they are functions that take an Animal as a parameter and return String.

One situation where this gets more interesting is when you want to return a specific T (where T <: Animal). Keep in mind, the bytecode will still be the same, but at compile-time this gives more meaning and power to the T type parameter:

Imagine you have:

def f1[T <: Animal](a: T): T = a  // silly, I know
def f2(a: Animal): Animal = a

And you try this:

val s: Dog = f1(new Dog())
val t: Dog = f2(new Dog()) // NOPE
val u: Dog = f2(new Dog()).asInstanceOf[Dog] // awkward

That second line won't compile without casting, which sacrifices compile-time type-checking.

Upvotes: 2

Wonpyo Park
Wonpyo Park

Reputation: 311

In Scala and JVM they have following function typing rule

S1 -> S2 is subtype of T1 -> T2

if and only if

S1 is subtype of T1 and T2 is subtype of S2

In your example,

   def f1[T <: Animal](a: T): String // T <: Animal -> String
   def f2(a: Animal): String // Animal -> String

By the function typing rule, f1 is the subtype of f2

In conclusion f1 and f2 has no difference in real use cases

Please refer, the following link

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Function_types

Upvotes: 0

Victor Moroz
Victor Moroz

Reputation: 9225

I believe there are no advantages in your example. Type parameters are usually used to connect different parts of the code, but information about T is effectively lost as it doesn't match anything. Consider another example:

def f1[T <: Animal](a: T) = (a.sound, a)

def f2(a: Animal) = (a.sound, a)

Now things are different as T is passed to a return type:

class Dog extends Animal { def sound = "bow-wow" }

f1(new Dog)
//> (String, Dog) = (bow-wow,Dog@141851fd)

f2(new Dog)
//> (String, Animal) = (bow-wow,Dog@5a1fe991)

In this case you can think of f1 as of a template which is "instantiated" at compile time and effectively generates a specific method based on compile-time parameter types. So if you want to use f1(new Dog) where (String, Dog) is required it will compile, while f2(new Dog) won't.

Upvotes: 5

Related Questions