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

I was wondering if there is a good reason to use a subtype as a function type parameter? 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 

Does f1 have some advantages over f2?

+6
source share
4 answers

I believe that in your example there are no advantages. Type parameters are usually used to connect different parts of the code, but information about T actually lost because it does not correspond to anything. Consider another example:

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

Now everything is different, since T is passed to the 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 a template that is "instantiated" at compile time and effectively generates a specific method based on the types of compilation time parameters. Therefore, if you want to use f1(new Dog) where it is required (String, Dog) , it will compile, and f2(new Dog) will not.

+5
source

Both functions f1 and f2 very similar. If you print the bytecode, you will see in the pool of constants that they are indicated as:

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

As for the bytecode, these are functions that take Animal as a parameter and return String .

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

Imagine that you have:

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

And you will try this:

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

This second line will not compile without a cast that sacrifices a compilation type check.

+2
source

Given your example, when f1 and f2 have the same type of output, the function f2 takes precedence over f1 if you want to overload your method.

So for f1 this 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 ^ 

and 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 
+1
source

In Scala and JVM, they have the following function input rule

S1 -> S2 is a subtype of T1 -> T2

if and only if

S1 is a subtype of T1 and T2 is a subtype of S2

In your example

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

According to the typification rule of a function, f1 is a subtype of f2

In conclusion, f1 and f2 does not differ in real cases of use

Please refer to the following link

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

0
source

Source: https://habr.com/ru/post/1012692/


All Articles