yoshikit1996’s diary

日々勉強したことの備忘録です。

トレイトのwithとextendsの使い分け

withとextendsの使い分けがややこしいと思うのでメモ。

文法的な使いわけ

  • クラス定義時にミックスインする場合、extendsした後にwithを使う。
  • インスタンス生成時にトレイトをミックスインする場合、withを使う。
object Main extends App{
    // クラス定義時にトレイトをミックスイン
    class MyQueue extends BasicIntQueue with Incrimenting
    // インスタンス生成時にトレイトをミックスイン
    val queue = (new BasicIntQueue with Incrimenting with Filtering)    
}

ハマったところ

インスタンス生成時にトレイトをミックスした場合の挙動」と「クラス定義時にミックスインした場合の挙動」は
同じだと思っていたら、ハマりました。実際は異なるみたいです。

下記に例を示します。

object Main extends App{
    // インスタンス生成時にトレイトをミックスインする場合
    {
        case class MyString(value: String){
            override def toString = value
        }
        
        trait Hoge{
            override def toString = super.toString + "hoge"
        }
        
        trait Fuga{
            override def toString = super.toString + "fuga"
        }
        
        val myString = new MyString("foobar") with Hoge with Fuga
        
        println(myString) // foobarhogefuga
    }
    
    // クラス定義時にトレイトをミックスインする場合
    {
        case class MyString(value: String) extends Hoge with Fuga{
            override def toString = value
        }
        
        trait Hoge{
            override def toString = super.toString + "hoge"
        }
        
        trait Fuga{
            override def toString = super.toString + "fuga"
        }
        
        println(MyString("foobar")) // foobar
    }
}

両方とも「foobarhogefuga」になるかと思いきや、クラス定義時にトレイトをミックスインすると「foobar」になりました。

「foobar」になる理由はおそらく↓こうだと思います。

  1. HogeにFugaがミックスインされる
  2. Hoge#toStringをMyString#toStringがオーバーライドしてしまう
  3. 結果、MyString#toStringがvalueを返して、foobarになる

ハマった理由

「withとextendsは、使い方が異なるだけで、同じもの」と思っていました。
また、「線形化」という言葉とwithとextendsの使い分けを理解していなかったみたいです。

誤った理解

extendsの前にくるのはミックスインの対象。
extendsおよびwithの後ろにくるトレイトはミックスインの対象に対して線形化される。

ミックスインの対象 extends trait1 with trait2 with ...
正しい理解

withの前にくるのはミックスインの対象。
withの後ろにくるトレイトはミックスインする対象に対して線形化される。

ミックスインの対象 with trait1 with trait2 with ...

trait1がミックスインの対象、trait2以降のトレイトはtrait1に対して線形化される。

クラス extends trait1 with trait2 with ...