Type members

A type member is similar to the type parameter, but it is defined as a type alias that's a member of an abstract class or trait. It can then be made concrete at the moment the abstract definition itself is made concrete. Let's look at the following few lines of code, which will show you how this works:

trait HolderA {
type A
def a: A
}
class A extends HolderA {
override type A = Int
override def a = 10
}

Here, we defined an abstract type member, A, and overrode it in the concrete implementation by binding it to the Int.

It is possible to define multiple type members, of course, and define constraints on them, including type members themselves as part of the constraint:

trait HolderBC {
type B
type C <: B
def b: B
def c: C
}

Type inference is not applied in this case, so the following code will not compile because the type definition is missing:

class BC extends HolderBC {
override def b = "String"
override def c = true
}

These type members can be defined using all language features that can be applied to other type definitions, including multiple type constraints and path-dependent types. In the following example, we demonstrate this by declaring type members in the HolderDEF and providing the concrete definition in the class DEF. Incompatible type definitions are noted as such and commented out:

trait HolderDEF {
type D >: Null <: AnyRef
type E <: AnyVal
type F = this.type
def d: D
def e: E
def f: F
}

class DEF extends HolderDEF {
override type D = String
override type E = Boolean

// incompatible type String
// override type E = String
// override def e = true

override def d = ""
override def e = true

// incompatible type DEF
// override def f: DEF = this

override def f: this.type = this
}

It is also possible to combine type members and type parameters and use them later to further constrain possible definitions of the former:

abstract class HolderGH[G,H] {
type I <: G
type J >: H
def apply(j: J): I
}
class GH extends HolderGH[String, Null] {
override type I = Nothing
override type J = String
override def apply(j: J): I = throw new Exception
}

Type members and type parameters look very similar in their function—this is done to define abstract type definitions that can be refined later. Given this similarity, a developer can use one or another most of the time. Still, there are a couple of nuances regarding the situations in which you should prefer to use them.

These type parameters are usually more straightforward and easier to get right, so generally, they should be preferred. Type members are the way to go if you run into one of the following cases:

  • If the concrete type definition should remain hidden
  • If the intended way to provide the concrete definition of the type is via inheritance (overridden in subclasses or mixed-in via traits)

There is another simple rule that's easy to memorize—type parameters are used to define the types of parameters of the method and type members to define the result type of this method:

trait Rule[In] {
type Out
def method(in: In): Out
}

There is also another way to specify boundaries for type parameters and type members in Scala.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.179.220