layout | title | partof | num | next-page | previous-page | topics | prerequisite-knowledge | redirect_from | ||
---|---|---|---|---|---|---|---|---|---|---|
tour | Abstract Type Members | scala-tour | 25 | compound-types | inner-classes | abstract type members | variance, upper-type-bound |
|
Abstract types, such as traits and abstract classes, can in turn have abstract type members. This means that the concrete implementations define the actual types. Here's an example:
{% tabs abstract-types_1 class=tabs-scala-version %} {% tab 'Scala 2' for=abstract-types_1 %}
traitBuffer { typeTvalelement:T }
{% endtab %} {% tab 'Scala 3' for=abstract-types_1 %}
traitBuffer:typeTvalelement:T
{% endtab %} {% endtabs %}
Here we have defined an abstract type T
. It is used to describe the type of element
. We can extend this trait in an abstract class, adding an upper-type-bound to T
to make it more specific.
{% tabs abstract-types_2 class=tabs-scala-version %} {% tab 'Scala 2' for=abstract-types_2 %}
abstractclassSeqBufferextendsBuffer { typeUtypeT<:Seq[U] deflength= element.length }
{% endtab %} {% tab 'Scala 3' for=abstract-types_2 %}
abstractclassSeqBufferextendsBuffer:typeUtypeT<:Seq[U] deflength= element.length
{% endtab %} {% endtabs %}
Notice how we can use yet another abstract type U
in the specification of an upper-type-bound for T
. This class SeqBuffer
allows us to store only sequences in the buffer by stating that type T
has to be a subtype of Seq[U]
for a new abstract type U
.
Traits or classes with abstract type members are often used in combination with anonymous class instantiations. To illustrate this, we now look at a program which deals with a sequence buffer that refers to a list of integers:
{% tabs abstract-types_3 class=tabs-scala-version %} {% tab 'Scala 2' for=abstract-types_3 %}
abstractclassIntSeqBufferextendsSeqBuffer { typeU=Int } defnewIntSeqBuf(elem1: Int, elem2: Int):IntSeqBuffer=newIntSeqBuffer { typeT=List[U] valelement=List(elem1, elem2) } valbuf= newIntSeqBuf(7, 8) println("length = "+ buf.length) println("content = "+ buf.element)
{% endtab %} {% tab 'Scala 3' for=abstract-types_3 %}
abstractclassIntSeqBufferextendsSeqBuffer:typeU=IntdefnewIntSeqBuf(elem1: Int, elem2: Int):IntSeqBuffer=newIntSeqBuffer:typeT=List[U] valelement=List(elem1, elem2) valbuf= newIntSeqBuf(7, 8) println("length = "+ buf.length) println("content = "+ buf.element)
{% endtab %} {% endtabs %}
Here the factory newIntSeqBuf
uses an anonymous class implementation of IntSeqBuffer
(i.e. new IntSeqBuffer
) to set the abstract type T
to the concrete type List[Int]
.
It is also possible to turn abstract type members into type parameters of classes and vice versa. Here is a version of the code above which only uses type parameters:
{% tabs abstract-types_4 class=tabs-scala-version %} {% tab 'Scala 2' for=abstract-types_4 %}
abstractclassBuffer[+T] { valelement:T } abstractclassSeqBuffer[U, +T<:Seq[U]] extendsBuffer[T] { deflength= element.length } defnewIntSeqBuf(e1: Int, e2: Int):SeqBuffer[Int, Seq[Int]] =newSeqBuffer[Int, List[Int]] { valelement=List(e1, e2) } valbuf= newIntSeqBuf(7, 8) println("length = "+ buf.length) println("content = "+ buf.element)
{% endtab %} {% tab 'Scala 3' for=abstract-types_4 %}
abstractclassBuffer[+T]:valelement:TabstractclassSeqBuffer[U, +T<:Seq[U]] extendsBuffer[T]:deflength= element.length defnewIntSeqBuf(e1: Int, e2: Int):SeqBuffer[Int, Seq[Int]] =newSeqBuffer[Int, List[Int]]:valelement=List(e1, e2) valbuf= newIntSeqBuf(7, 8) println("length = "+ buf.length) println("content = "+ buf.element)
{% endtab %} {% endtabs %}
Note that we have to use variance annotations here (+T <: Seq[U]
) in order to hide the concrete sequence implementation type of the object returned from method newIntSeqBuf
. Furthermore, there are cases where it is not possible to replace abstract type members with type parameters.