Scala type parameter bracket

2020-06-05 08:09发布

问题:

I know trait Foo[T] means T is a parametrized type. But some times I can see trait Foo[T1,T2], or trait Foo[T1,T2,R], I cannot find anywhere describe the meaning of multiple types inside a type bracket, could you please point me the usages in this case? From what I speculate, Foo[T1,T2] just means, it defined two type parameters, it doesn't have to be take a T1 and return a T2.

When I read playframework documentation today, I again found myself confused about this question. In the documentation, it says:

A BodyParser[A] is basically an Iteratee[Array[Byte],A], meaning that it receives chunks of bytes (as long as the web browser uploads some data) and computes a value of type A as result.

This explanation sounds like, the second the type parameter inside a type bracket is a return type.

I also remember that trait Function2 [-T1, -T2, +R] extends AnyRef means a function that takes a T1 and T2, return a R.

Why do they put the return type in the bracket? Does it mean all the last parameter in a bracket is a return type? Or they just happened defined a new type R for the return type?

回答1:

Multiple types inside a type bracket means type parametrization on multiple types. Take for example

trait Pair[A, B]

This is a pair of values one having type A the other having type B.

Update:

I think you are interpreting too much into the semantics of type parameters. A type parametrized by multiple parameters is just that and nothing more. The position of a specific type parameter in the list of type parameters does not make it special in any way. Specifically the last parameter in a list of type parameters does not need to stand for 'the return type'.

The sentence from the play framework which you quoted explains the semantics of the type parameters for this one specific type. It does not generalize to other types. The same holds for the Function types: here the last type parameter happens to mean 'the return type'. This is not necessarily the case for other types though. The type Pair[A, B] from above is such an example. Here B is the type of the second component of the pair. There is no notion of a 'return type' here at all.

Type parameters of a parametrized type can appear anywhere inside the definition of the parametrized type where a 'regular' type could appear. That is, type parameters are just names for types which are bound to the actual types only when the parametrized type itself is instantiated.

Consider the following definition of a class Tuple:

class Tuple[A, B](a: A, b: B)

It is instantiated to a type of a tuple of Int and String like this:

type TupleIntString = Tuple[Int, String]

Which is essentially the same as

class TupleIntString(a: Int, b: String)     

For an official source check the Scala Language Specification. Specifically Section 3.4 "Base Types and Member Definitions" under 1. the 4th bullet point says: "The base types of a parameterized type C[T_1, ..., T_n] are the base types of type C , where every occurrence of a type parameter a_i of C has been replaced by the corresponding parameter type T_i."



回答2:

From what I speculate, Foo[T1,T2] just means, it defined two type parameters, it doesn't have to be take a T1 and return a T2.

A type parameter means nothing more than "I need any type but I'm not interested to know what it is for a concrete type", where 'I' is the programmer who writes the code. Type parameters can used like any other types such as Int, String or Complex - the only difference is that they are not known until one uses them.

See type Map[A, +B]. When you first read this, you can't know what the A and B are for, thus you have to read the documentation:

A map from keys of type A to values of type B.

It explains the types and their meaning. There is nothing more to know or understand. They are just two types. It is possible to call Map something like Map[Key, Value] but inside of source code it is better when type parameters have only one or two letters. This makes it easier to differ between the type parameters and concrete types.

It is the documentation which specifies what a type parameter means. And if there is no documentation you have to take a look to the sources and find their meaning by yourself. For example you have to do this with Function2 [-T1, -T2, +R]. The documentation tells us only this:

A function of 2 parameters.

Ok, we know that two of the three type parameters are parameters the function expects, but what is the third one? We take a look to the sources:

def apply(v1: T1, v2: T2): R

Ah, now we know that T1 and T2 are the parameters and that R is a return type.

Type parameters also can be found in method signatures like map:

class List[+A] {
  ..
  def map[B](f: (A) ⇒ B): List[B]
}

This is how map looks like when you use it with a List. A can be any type - it is the type of the elements a list contains. B is another arbitrary type. When you know what map does then you know what B does. Otherwise you have to understand map before. map expects a function which can transform each element of a List to another element. Because you know that A stands for the elements of the List you can derive from yourself that B have to be the type A is transformed to.

To answer all your other questions: This shouldn't be done in a single answer. There are a lot of other questions and answers on StackOverflow which can also answer your questions.

Summary

When you see some type parameters for example in Foo[T1, T2] you should not start to cry. Think: "Ok, I have a Foo which expects a T1 and a T2 and if I want to know what they do I have to read documentation or the sources."



回答3:

I think your question can actually be broken in three separate problems:

What's with the multiple type parameters for classes/traits/etc. ?

A classic example is a map from one type of object to another. If you want the type for the keys to be different from type of the value, but keep both generic, you need two type parameters. So, a Map[A,B] takes keys of generic type A and maps to values of generic type B. A user that wants a map from Bookmarks to Pages would declare it as Map[Bookmark, Page]. Having only one type parameters would not allow this distinction.

From what I speculate, Foo[T1,T2] just means, it defined two type parameters, it doesn't have to be take a T1 and return a T2.

No, all type parameters are equal citizens, though they have a meaning in that direction for function objects. See below.

What are all those +/-'s ?

They limit what the type parameters can bind to. The Scala by Example tutorial has a good explanation. See Section 8.2 Variance Annotations.

What is a function in scala?

Why do they put the return type in the bracket? Does it mean all the last parameter in a bracket is a return type? Or they just happened defined a new type R for the return type?

The Scala by Example tutorial explains this well in Section 8.6 Functions.



回答4:

Their role is a bit similar to the ones (i.e. multiple type parameter) in a class, since traits are, after all, classes (without any constructor) meant to be added to some other class as a mixin.

The Scala spec gives the following example for Trait with multiple parameters:

Consider an abstract class Table that implements maps from a type of keys A to a type of values B.
The class has a method set to enter a new key/value pair into the table, and a method get that returns an optional value matching a given key.
Finally, there is a method apply which is like get, except that it returns a given default value if the table is undefined for the given key. This class is implemented as follows.

abstract class Table[A, B](defaultValue: B) {
  def get(key: A): Option[B]
  def set(key: A, value: B)
  def apply(key: A) = get(key) match {
    case Some(value) => value
    case None => defaultValue
  }
}

Here is a concrete implementation of the Table class.

class ListTable[A, B](defaultValue: B) extends Table[A, B](defaultValue) {
  private var elems: List[(A, B)]
  def get(key: A) = elems.find(._1.==(key)).map(._2)
  def set(key: A, value: B) = { elems = (key, value) :: elems }
}

Here is a trait that prevents concurrent access to the get and set operations of its parent class

trait Synchronized Table[A, B] extends Table[A, B] {
  abstract override def get(key: A): B =
  synchronized { super.get(key) }
  abstract override def set((key: A, value: B) =
    synchronized { super.set(key, value) }
}

Note that SynchronizedTable does not pass an argument to its superclass, Table, even though Table is defined with a formal parameter.
Note also that the super calls in SynchronizedTable’s get and set methods statically refer to abstract methods in class Table. This is legal, as long as the calling method is labeled abstract override (§5.2).

Finally, the following mixin composition creates a synchronized list table with strings as keys and integers as values and with a default value 0:

object MyTable extends ListTable[String, Int](0) with SynchronizedTable

The object MyTable inherits its get and set method from SynchronizedTable.
The super calls in these methods are re-bound to refer to the corresponding implementations in ListTable, which is the actual supertype of SynchronizedTable in MyTable.



标签: scala