Reputation: 337
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
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
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
Reputation: 311
In Scala and JVM they have following function typing rule
S1 -> S2
is subtype ofT1 -> T2
if and only if
S1
is subtype ofT1
andT2
is subtype ofS2
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
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