In this simplified experiment, I want to be able to quickly build a class with stackable traits that can report on what traits were used to build it. This reminds me strong
Here is an example of preferring composition. The amplification logic is refactored.
I find I must use abstract override once or twice a year or else that brain cell will die.
In this example, the animal becomes noisier as you mix in more Noise.
It uses runtime reflection, but of course you could imagine a macro doing something similar. (You'd have to tell it what this is.)
Real code would of course perform more interesting transforms; for instance, a pig noise mixed in after a duck noise would sound like a goose just delivering an egg.
package sounds
trait Sound {
def sound: String
}
trait Silent extends Sound {
def sound: String = ""
}
// duck is always funnier
trait Duck extends Silent
object Amplifier {
import reflect.runtime.currentMirror
import reflect.runtime.universe._
def apply[A <: Sound : TypeTag](x: Any): Int = {
val im = currentMirror reflect x
val tpe = im.symbol.typeSignature
var i = -1
for (s <- tpe.baseClasses) {
if (s.asClass.toType =:= typeOf[A]) i = 0
else if (s.asClass.toType <:< typeOf[Noise]) i += 1
}
i
}
}
trait Noise
trait NoisyQuack extends Sound with Noise {
abstract override def sound: String = super.sound + noise * amplification
private val noise = "quack"
private def amplification: Int = Amplifier[NoisyQuack](this)
}
trait NoisyGrunt extends Sound with Noise {
abstract override def sound: String = super.sound + noise * amplification
private val noise = "grunt"
private def amplification: Int = Amplifier[NoisyGrunt](this)
}
object Test extends App {
val griffin = new Duck with NoisyQuack with NoisyGrunt {
override def toString = "Griffin"
}
Console println s"The $griffin goes ${griffin.sound}"
}