Target audience: Intermediate
Estimated reading time: 4'
This post illustrates the appropriate use of self-type to restrict the composition of (stackable) traits (mixins) in relation to an existing class or trait.
Overview
Mixin traits with self-type restriction has commonly used in Scala. Dependency injection and the Cake pattern in particular, relies on constraint imposed by a trait that it can be used only with subclass of a predefined types. The same approach can be used to constraint a trait to be used with class that support one or several methods.
Note: For the sake of readability of the implementation of algorithms, all non-essential code such as error checking, comments, exception, validation of class and method arguments, scoping qualifiers or import is omitted.
Mixin constraint on self-type
Let's consider the case of a class taxonomy (or hierarchy) that classifies machine learning algorithms as either supervised learning or unsupervised learning.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | trait Learning {
def predict(x: Double): Double { }
}
trait SupervisedLearning {
def train(x: Array[Double]): Int = { ... }
}
trait Validation {
self: SupervisedLearning =>
def validate(x: Array[Double]): Double
}
class SVM
extends SupervisedLearning
with Validation {
override def train(x: Array[Double]): Int = { ... }
}
|
The support vector machine of type SVM is a type of supervised learning algorithm, and therefore extends the SupervisedLearning trait (line 5 & 115). The code snippet compiles because the class SVM (line 14) complies with the restriction imposed by the trait Validation on sub-types of SupervisedLearning (line 10).
1
2
3
4
5
6
7
8
9
10
11
12 | trait UnsupervisedLearning {
def group(x: Array[Double]): int
}
// Validation: failed self-typed inheritance
// from SupervisedLearning trait
class EM
extends UnupervisedLearning
with Validation {
override def train(x: Array[Double]): Int = { ... }
}
|
The Scala code snippet does not compile because the expectation-maximization algorithm, EM is an unsupervised learning algorithm and therefore is not a sub-class of SupervisedLearning.
Mixin constraint on self-typed method
Marking a trait to be extended (implemented) with sub-class with predefined method(s) is the same as marking a trait to be extended (implemented) with sub-class with predefined type.
Let's reuse the class hierarchy introduced in the previous section.
Let's reuse the class hierarchy introduced in the previous section.
trait Validation {
self: { def train(x: Array[Double]): Int } =>
def validate(x: Array[Double]): Double = -1.0
}
This time around the Validation can be mixed with a class that implements the method train
As previously seen, the class SVM complies with the restriction imposed by the Validation. However the declaration of the reinforcement learning algorithm QLearning generated a compilation error because it does not implement the method train as required.
// No training needed for Q-Learning
class QLearning
extends Learning
with Validation{
def predict(x: Double): Double { }
}
Although brief, this introduction to self-referential condition should help you to start considering this technique to protect you code from unintentional erroneous sub-typing and trait mixing.
References
- Scala Cookbook A. Alexander O' Reilly 2013
- The Scala Programming Language - M. Odersky, L. Spoon, B.Venners - Artima 2007
- github.com/patnicolas