It appears there are three (or more) ways to limit which classes can mix-in a given scala trait:
- Using a common ancestor [trait]
- Using abstract declaration
- Using self-type in the trait
The common ancestor method requires additional restrictions and it seems suboptimal. Meanwhile, both self-typing and abstract declarations seems to be identical. Would someone care to explain the difference and use-cases (especially between 2 & 3)?
My example is:
val exampleMap = Map("one" -> 1, "two" -> 2)
class PropsBox (val properties : Map[String, Any])
// Using Common Ancestor
trait HasProperties {
val properties : Map[String, Any]
}
trait KeysAsSupertype extends HasProperties {
def keys : Iterable[String] = properties.keys
}
class SubProp(val properties : Map[String, Any]) extends HasProperties
val inCommonAncestor = new SubProp(exampleMap) with KeysAsSupertype
println(inCommonAncestor.keys)
// prints: Set(one, two)
// Using Abstract Declaration
trait KeysAsAbstract {
def properties : Map[String, Any]
def keys : Iterable[String] = properties.keys
}
val inAbstract = new PropsBox(exampleMap) with KeysAsAbstract
println(inSelfType.keys)
// prints: Set(one, two)
// Using Self-type
trait KeysAsSelfType {
this : PropsBox =>
def keys : Iterable[String] = properties.keys
}
val inSelfType = new PropsBox(exampleMap) with KeysAsSelfType
println(inSelfType.keys)
// prints: Set(one, two)
In your example,
PropsBoxdoes not impose any interesting constraints onproperties- it simply has a memberproperties: Map[String, Any]. Therefore, there is no way to detect the difference between inheriting fromPropsBoxand simply requiring adef properties: Map[String, Any].Consider the following example, where the difference is actually there. Suppose we have two classes
GoodBoxandBadBox.GoodBoxhasproperties, and all keys are short string that contain only digitsBadBoxjust hasproperties, and does not guarantee anything about the structure of the keysIn code:
Now suppose that we for some reason want to transform the
Map[String, Any]into a sparsely populatedArray[Any], and use keys as array indices. Here, again, are two ways to do this: one withself-type declaration, and one with the abstractdef propertiesmember declaration:Now try it out:
With a
goodBox, both methods will work and produce the same results. However, with abadBox, the self-type vs. abstract-def behave differently:NumberFormatException(error happens at runtime)That's the difference.