Study note of the chapter 19, 20, and 21 of the book: Programming In Scala, 4th Edition.

Chapter 19: Type Parameterization

19.1 Introduction

Type parameterization allows type contructors using generic classes and traits. A constructor type of genecric class or trait is not a regular data type. A constructed type with all specified type arguments is called a parameterized type. Varianace defines subtype relationships of parameterized types.

The List class uses a fully persistent data structure, where old versions remain available even after extensions or modifications.

Scala doesn’t have an explicit primary constructor. The primary constructor is defined implicitly by the class parameters and body. You can make it private by putting a private modifier in front of the class parameter list.

You can define a public trait and define a private class in a singleton object.

19.2 Variance

If S is a subtype of T, then

  • if F[_] is covariant/flexible, F[S] is a subtype of F[T].
  • if F[_] is contravariant, F[S] is a supertype of F[T].
  • if F[_] is novariant/regid, there is no subtype relationship between F[S] and F[T].

In Scala, Array is novariant. The asInstanceOf[Array[SuperType]] allows an array to be casted as an array of its supert type.

The compiler checks that type parmaters are used in right positions. Covriant types must be in positive position, controvriant types in negative position, and novariant types in neutral position.

To classify the positions, the compiler starts from outside to the inside. usually the nesting levels are classified the same as the enclosing level, with the following exceptional rules:

  • Method value parameter positions are classified to the flipped classification. The flip of a positive is a negative, a flip of a negative is a positive and a flip of a neutral is neutral.
  • The position of the type parameter of a method is also flipped.
  • The classification of the position of the type parameter of a type depends on the variance of the corresponding type parameter. For covariant type, the position stays the same. For contravariant, the it flips. For novariant, the position is neutral.

It is worth pointing out that var class parameters are treated as methods because the compiler generates getter methods for them. An exeption is no checking is done if the class parameters are object private by having a private[this] modifier.

The [U >: T] syntax defines a lower bound T for U. U is in a negative position and T is in a positive position. The lower bound is often used to obtain covariance.

The [U <: T] syntax defines an upper bound T for U. U is in a negative position and T is in a positive position. Upper bound is not commonly used in Scala alone because implicit parameter and context bound often provide better solutions.

Chapter 20: Abstract Members

20.1 Abstract Memebers

Scala allows abstract fields include method, val, var and type. An abstract type in scala is a type T member, without defintion, of a class or trait. Abstract classes or abstract traits are not abstract types.

A type member has two purpose: (1) a short, descriptive alias for a concrete type and (2) an abstract type that must to be defined in subclasses.

An abstract val defines a value that stays the same after initialization. It cannot be overridden by an abstract var or an abstract method.

A class parameter argument is evaluated before it is passed to the class constructor. A val defintion in a subclass, is evaluated after the superclass has been iinitialized. Use pre-initialized vals or lazy vals.

Singleton objects are lazy.

Upper bound types are often used with abstract types to specify that type constraints for subclasses.

20.2 Path-dependent Type

Type defined in a class or an object is a path-dependent type that can be referenced using an object identity, for example a.T or b.T. The type of different objects that has the same class share the same path-dependent type.

Inner classes are path-dependent type. However, different objects don’t share the same inner class. All objects’ inner class are subtypes of their general type Outer#Inner.

The # is used for classes and . is used for bojects. An inner class must hold a reference to an enclosing outer class instance to allow the inner class to access members of its outer class.

The Enumeration defines an inner class Value and a method Value that returns an instance of the path dependent class Value. Therefore each subtype of Enumeration has a different path-dependent type of the Value inner class.

When a class inherits from another, the first is sadi to be a nominal subtype of the other one. It is nominal because it has a name. Scala also supports structural subtype via refinement type. A structural subtype has compatible members to its parent. to use refinement type, simple write the base type, followed by a sequence of members listed in curly braces.

Chapter 21: Implicit Conversioins and Parameters

Implict defintions are converts that the compiler inserts into a program in order to fix a type errors. Implicits are used in three places:

  • conversions to an expected type
  • coversions to the receiver of a method call
  • implicit function parameters.

21.1 Implicit Conversion Rules

The implicit conversions are governed by the following rules:

  • Only defintions marked implicit are available.
  • The converter must be a single identifier in scope, or be associated with the source or target type. For example, in the companion object of the source type or target type. The type could be high kind type such as F[A] and both F and A’s companion object are available.
  • Only one implicit is inserted.
  • Explicit converter has high priority. Insert happens only when there is a type error.

The receiver conversion servers two purposes: integration of new classes by converting existing ones that can work with the new classes, often using existing methods; supporting new methods to use domain specific languages (DSL).

21.2 Implict Class

An implicit class is a class that is marked by the implicit keyword. According to the SIP-13-implicit class, the purpose is to provide extension methods to another type. The compiler generates an implicit conversion from the class’s constructor parametr to itself. Therefore defining an implicit class also defines a conversion method and it comes with three limitations:

  • It must be defined where method defintions are allowed, i.e., within some other object, class, or trait. The compiler will generate a class and an implicit method.
  • An implicit class cannot be a case class. Because the generated method has a name conficting with the generated companinon object of the case class.
  • Its constructor must have exactly one parameter. The conversion is one to one.

21.3 Implicit Parameter

Implicit parameter is used in method calls and new class instances. It is used to provide more support functions for an earlier parameter list. It is better than upper bound because upper bound requires a stronger is-a constraint where the implicit parameter only provides additional functions without changing the existing types.

Though an anonymous function type is enough, it is a style rule to use a meaningful name for the implicit parameter type. This gives the purpose of the function and reduce the chances of unintentional imiplicit usage.

The standard library define an implicitly method: def implicitly[T](implicit t: T) = t. Thus t can be replaced by implicitly[T] when there is an implicit t in scope.

The context bound of [A: T] adds an implicit parameter of (implicit t: T[A]) as the last parameter list.

Working together, the imiplict class and the implict parameter allow converting a type T to another type F to uses methods defined in F. The F[T] is called a type class.

21.4 Resolving Ambiguity

Scala reports an error if there are multiple conversions available. Use specific rules to avoid the multiple conversion problem. An implicit conversion is more specific than antoher if

  • The argument type of the first is a subtype ofanother’s
  • Both conversions are methods, the enclosing class of the first extends the enclosing class of the latter.

To use the second rule, it is often to define low prirority more-general conversions in a base type and high priority more-specific conversions in a subtype.

Use -Xprint:typer to see the implicit converters being applied.