Reputation: 33
I have a question about trait constructor order.
class Account(initialBalance: Double) extends ConsoleLogger {
private var balance = initialBalance
def withdraw(amount: Double) = {
if (amount > balance) log("Insufficient funds")
else {
balance -= amount;
balance
}
}
def getAccount = balance
}
trait Logged {
def log(msg: String): Unit = {}
}
trait ConsoleLogger extends Logged {
override def log(msg: String): Unit = {
println(msg)
}
}
trait TimestampLogger extends Logged {
override def log(msg: String) {
super.log(new Date() + " " + msg)
}
}
trait ShortLogger extends Logged {
val maxLength = 15
override def log(msg: String) {
super.log(if (msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")
}
}
object test9 extends App {
val acct1 = new Account(100) with ConsoleLogger with TimestampLogger with ShortLogger
val acct2 = new Account(100) with ConsoleLogger with ShortLogger with TimestampLogger
acct1.withdraw(500)
acct2.withdraw(500)
}
the result is :
> Sun Sep 20 21:25:57 CST 2015 Insufficient...
> Sun Sep 20 2...
In acct1, the first is ShortLogger.log, the second is TimestampLogger.log. In acct2, the first is TimestampLogger.log, the second is ShortLogger.log.
But as I know, trait constructor order is from left to right.
So acct1's trait constructor order is:
Logged, ConsoleLogger, TimestampLogger, ShortLogger.
Why is ShortLogger.log executed first?
Upvotes: 0
Views: 669
Reputation: 631
It's related to Linerization
object Main extends App {
val acct1 = new SavingsAccount with ShortLogger
val acct2 = new SavingsAccount with TimeStampLogger
val acct3 = new SavingsAccount with TimeStampLogger with ShortLogger
val acct4 = new SavingsAccount with ShortLogger with TimeStampLogger
acct1.withdraw(100)
acct2.withdraw(100)
acct3.withdraw(100)
}
To understand the behaviour I have created acct1 and acc2:
acct1.withdraw(100) // => Insufficient...
acct2.withdraw(100) // => Wed Apr 05 11:59:09 EEST 2017 Insufficient amount
acct3.withdraw(100) // => Wed Apr 05 11:59:09 EEST 2017 Insufficient...
The behaviour of acct3: /* +:
denotes concatenation */
acct3 = acct1 +: acct2
Note: Duplicates in acct2 is removed when acct1 is applied
under the hood:
L(Account) = Account + L(ShortLogger) + L(TimestamLogger) + L(ConsoleLogger)
L(Account) = Account + ShortLogger + Logged + TimestamLogger + Logged + ConsoleLogger + Logged
Same method could be applied for acct4
Upvotes: 1
Reputation: 1685
Actually, it's all about linearization.
Definition 5.1.2 Let C be a class with template C1 with ... with Cn { stats }. The linearization of C, L(C) is defined as follows: L(C) = C , L(Cn)+: ... +: L(C1)
Here +: denotes concatenation where elements of the right operand replace identical elements of the left operand.
You can check this question, the answer is very clear. Basically, the idea is that in order to figure out which method is called first, you must linearize your class. So with the first example :
new Account(100) with ConsoleLogger with TimestampLogger with ShortLogger
L(Account) = Account + L(ShortLogger) + L(TimestamLogger) + L(ConsoleLogger)
L(Account) = Account + ShortLogger + Logged + TimestamLogger + Logged + ConsoleLogger + Logged
Linearization requires to remove all duplicates except the last one :
L(Account) = Account + ShortLogger + TimestamLogger + ConsoleLogger + Logged
So in this example, the first call to log will trigger the one defined in ShortLogger, then TimestamLogger, and so on. With the second example :
new Account(100) with ConsoleLogger with ShortLogger with TimestampLogger
L(Account) = Account + L(TimestampLogger) + L(ShortLogger) + L(ConsoleLogger)
L(Account) = Account + TimestampLogger + Logged + ShortLogger + Logged + ConsoleLogger + Logged
L(Account) = Account + TimestampLogger + ShortLogger + ConsoleLogger + Logged
Here, the first method called is the one from TimestampLogger, then ShortLogger, and ConsoleLogger. Is this answering your question ?
Upvotes: 1