Target audience: Intermediate
Estimated reading time: 10'
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
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
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 theSupervisedLearning 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
References
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 mixin 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 asd 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