ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Scalaの勉強 - トレイト(1)

スポンサーリンク

今日はトレイトの章を。

トレイト

概要

今までどの言語でも見たことのない用語。"trait == 特定とか特色"という意味らしい。

プログラムの分割(モジュール化)と組み立て(合成)は、オブジェクト指向プログラミングでも関数型プログラミングにおいても重要な設計の概念になります。そして、Scalaオブジェクト指向プログラミングにおけるモジュール化の中心的な概念になるのがトレイトです。

と書いてあるので、Scalaにおける重要な機能なのだろう。

では定義を。

trait <トレイト名> {
  (<フィールド定義> | <メソッド定義>)*
}

クラスの定義からコンストラクタ部分を除いたものという感じ。

以下のようなことが特徴として挙げられるとのこと。

  • 複数のトレイトを1つのクラスやトレイトにミックスインできる
  • 直接インスタンス化できない
  • クラスパラメータ(コンストラクタの引数)を取ることができない

PerlMooseとかにあるRoleみたいなものっぽい。

上記の3点について見ていく。

複数のトレイトを1つのクラスやトレイトにミックスインできる

Scalaのトレイトはクラスとは異なり、複数のトレイトを1つのクラスやトレイトにミックスイン出来る

trait TraitA

trait TraitB

class ClassA

class ClassB

class ClassC extends ClassA with TraitA with TraitB

object TraitMixin {
  def main(args: Array[String]): Unit = {
  }
}
$ scala 20180916_01_trait_mixin.scala
# エラーなく実行される

次にwith ClassBというふうにクラスを指定した場合を見てみる。

class ClassA

class ClassB

class ClassC extends ClassA with ClassB

object TraitMixinNG {
  def main(args: Array[String]): Unit = {
  }
}

上記のようにwithClassBを指定した場合はエラーになる。

$ scala 20180916_02_trait_mixin_ng.scala
20180916_02_trait_mixin_ng.scala:9: error: class ClassB needs to be a trait to be mixed in
class ClassC extends ClassA with ClassB
                                 ^
one error found

複数のクラスを継承する必要のある場合はクラスをトレイトにすることで実現が可能。

直接インスタンス化できない

そのままなので、試してみる。

trait TraitA

object TraitInstanceNG {
  def main(args: Array[String]): Unit = {
    val a = new Trait
  }
}

上記のコードを実行すると以下のようにエラーになる。

$ scala 20180916_03_trait_instance_ng.scala 
20180916_03_trait_instance_ng.scala:5: error: trait TraitA is abstract; cannot be instantiated
    val a = new TraitA
            ^
one error found

クラスパラメータ(コンストラクタの引数)を取ることができない

これもそのままでクラスでは出来たコンストラクタに引数を持たせるということが出来ない。

trait TraitA(name: String)

object TraitNotHasConstructorMemeberNG {
  def main(args: Array[String]): Unit = {
  }
}

上記のコードを実行すると以下のようにエラーになる。

$ scala 20180916_04_trait_not_has_member_ng.scala
/home/dnn-admin/workspace/hw/study/2000_chisel/200_examples/20180916_04_trait_not_has_member_ng.scala:1: error: traits or objects may not have parameters
trait TraitA(name: String)
            ^
one error found

定義で書いたようにトレイト自体はフィールドを持つことが可能だが、コンストラクタ引数が存在しないのでそれを使った値の設定が出来ないことになるが、これも特に問題にはならないらしく以下のように抽象メンバーを持たせることで値を設定可能にすることが出来る。

trait TraitA {
  val name: String
  def printName(): Unit = println(name)
}

class ClassA(val name: String) extends TraitA

object TraitAbstractMember {
  def main(args: Array[String]): Unit = {
    val a = new ClassA("trait")

    a.printName()

    // nameを上書きするような実装を与えることも可能
    val a2 = new TraitA { val name = "override" }

    a2.printName()
  }
}

上記のように定義したTraitAを継承したクラスを作成しコンストラクタの引数でメンバを上書きするとTraintAnameに値を設定することが出来る。

また、val a2のようにnameを上書きするようにして使用することも可能。このnew Trait {}を使うとTraitを継承した無名のクラスを作ってインスタンスすることが出来る。

$ scala 20180916_05_trait_abstract_member.scala
trait
override

まだこの章は続くのだけど、今日はここまで。