HongYi
HongYi

Reputation: 33

scala about trait constructor order

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

Answers (2)

ragmha
ragmha

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

Francis Toth
Francis Toth

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

Related Questions