Skip to content

Latest commit

 

History

History
141 lines (121 loc) · 4.2 KB

abstract-type-members.md

File metadata and controls

141 lines (121 loc) · 4.2 KB
layouttitlepartofnumnext-pageprevious-pagetopicsprerequisite-knowledgeredirect_from
tour
Abstract Type Members
scala-tour
25
compound-types
inner-classes
abstract type members
variance, upper-type-bound
/tutorials/tour/abstract-types.html
/tour/abstract-types.html

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.

close