Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 606
ptrlaszlo Avatar asked Nov 27 '16 22:11

ptrlaszlo


People also ask

What is a type parameter Scala?

Language. Methods in Scala can be parameterized by type as well as by value. The syntax is similar to that of generic classes. Type parameters are enclosed in square brackets, while value parameters are enclosed in parentheses.

What does a type parameter do?

A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.

How does Scala determine types when they are not specified?

For example, a type constructor does not directly specify a type of values. However, when a type constructor is applied to the correct type arguments, it yields a first-order type, which may be a value type. Non-value types are expressed indirectly in Scala.

What is [+ A in Scala?

It declares the class to be covariant in its generic parameter. For your example, it means that Option[T] is a subtype of Option[S] if T is a subtype of S . So, for example, Option[String] is a subtype of Option[Object] , allowing you to do: val x: Option[String] = Some("a") val y: Option[Object] = x.


2 Answers

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.

like image 93
Victor Moroz Avatar answered Nov 15 '22 06:11

Victor Moroz


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.

like image 35
drhr Avatar answered Nov 15 '22 06:11

drhr