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

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

Chisel Bootcamp - Module3.5(1) - Scalaの抽象クラスとトレイト

スポンサーリンク

前回のChiselの記事ではChisel-bootcampのModule3.4の練習問題でニューラルネットワークニューロン回路を実装するという問題に取り組んだ。

www.tech-diningyo.info

今回はModule3.5に入っていく。ここで扱うトピックはScala関数型言語とは別に存在するもうひとつの大きな特徴であるオブジェクト指向の仕組みについてだ。

オブジェクト指向プログラミング

モチベーション

これまで通りモチベーションから確認していく。

ScalaとChiselはオブジェクト指向言語であり、これはコードをオブジェクトに区分化しても良いことを意味する。Javaの上に構築されているScalaは多くのJavaオブジェクト指向の特徴を継承している。しかしながら、これから見ていくようにそこにはいくつかの違いがある。ChiselのハードウェアモジュールはVerilogのモジュールと似ており、ひとつのまたは複数のモジュールをインスタンスし配線することが可能だ。

オブジェクト指向プログラミング

これまでにもScalaのクラスは使用してきているが、それ以外にもScalaオブジェクト指向の特徴には以下のようなものがある。

  • 抽象クラス
  • トレイト
  • オブジェクト
  • コンパニオンクラス
  • ケース・クラス

ということでこのモジュールでは上記の5つについて、改めて例を使って見ていくようだ。

抽象クラス

抽象クラスは他のオブジェクト指向言語と同様の概念で、クラスの中に未実装の値やメソッドを定義することが出来る。 これらの未実装の値/メソッドは抽象クラスを継承したサブクラス側に実装を強制することが出来る。また未実装の値/メソッドが存在する関係もあり抽象クラス自体はインスタンスできない。 どのようなオブジェクトであってもひとつの抽象クラスのみを継承できる。

例題:抽象クラス

早速抽象クラスの例を見てみよう。抽象クラスはclassの定義時にキーワードabstractを付与することにより定義できる。 以下の例のMyAbstractClassを見るとわかる通りで、値やメソッドを宣言したのみであっても文法エラーにはならない。 一方でMyAbstractClassを継承したConcreteClassでは抽象クラスで宣言のみを行ったmyFunctionmyValueの実装が行われている。

abstract class MyAbstractClass {
  def myFunction(i: Int): Int
  val myValue: String
}
class ConcreteClass extends MyAbstractClass {
  def myFunction(i: Int): Int = i + 1
  val myValue = "Hello World!"
}
// 以下のコメント文をテストのために有効にしてみよう
// val abstractClass = new MyAbstractClass() // エラー! 抽象クラスのインスタンスは作れない
val concreteClass = new ConcreteClass()      // 文法的に正しい!

まずは上記をそのまま実行した結果が以下になる。正常にクラスが定義されconcreteClassインスタンスされたConcreteClassが格納される。

defined class MyAbstractClass
defined class ConcreteClass
concreteClass: ConcreteClass = ammonite.$sess.cmd2$Helper$ConcreteClass@552c2904

では、上記のサンプルのコメントにあるとおりコメントを外して抽象クラスをインスタンスしようとしてみよう。結果は以下のようになる。

cmd3.sc:10: class MyAbstractClass is abstract; cannot be instantiated
val abstractClass = new MyAbstractClass() // エラー! 抽象クラスのインスタンスは作れない
                    ^Compilation Failed

更にもうひとつ、最初に書いたとおり抽象クラスで定義された未実装の値/メソッドは継承先のクラスで実装が強制されることを確認しておこう。 下記のコードはConcreteClassのボディの部分をなくして、継承しただけのものだ。

abstract class MyAbstractClass {
  def myFunction(i: Int): Int
  val myValue: String
}
class ConcreteClass extends MyAbstractClass {
}
// 以下のコメント文をテストのために有効にしてみよう
// val abstractClass = new MyAbstractClass() // エラー! 抽象クラスのインスタンスは作れない
val concreteClass = new ConcreteClass()      // 文法的に正しい!

これを実行すると以下のようにエラーが発生する。

cmd3.sc:5: class ConcreteClass needs to be abstract, since:
it has 2 unimplemented members.
/** As seen from class ConcreteClass, the missing signatures are as follows.
 *  For convenience, these are usable as stub implementations.
 */
  def myFunction(i: Int): Int = ???
  val myValue: String = ???

class ConcreteClass extends MyAbstractClass {
      ^Compilation Failed

トレイト

次はトレイト。これは抽象クラスと似ている概念で未定義の値を定義することが出来る。しかし抽象クラスと比較すると以下のような違いが存在する。

  • クラスは複数のトレイトを継承できる
  • トレイトはコンストラクタ引数を持てない

例題:トレイトと複数の継承

早速、上記に上げた抽象クラスとの違いを確認するためのコードを見てみよう。 以下のコードでは作成したHasFunctionHasValueの中にそれぞれ未実装な部分が存在している。 それをMyClassで継承して使用するというサンプルになっている。 MyClassの宣言を見てのとおりだが、トレイトも最初に継承する際にはextendsキーワードを使用し、2個目以降はwithキーワードを使用する。

trait HasFunction {
  def myFunction(i: Int): Int
}
trait HasValue {
  val myValue: String
  val myOtherValue = 100
}
class MyClass extends HasFunction with HasValue {
  override def myFunction(i: Int): Int = i + 1
  val myValue = "Hello World!"
}
// 以下のコメント文をテストのために有効にしてみよう
// val myTraitFunction = new HasFunction() // 文法エラー! トレイトはインスタンス出来ない。
// val myTraitValue = new HasValue()       // 文法エラー! トレイトはインスタンス出来ない。
val myClass = new MyClass()                // 文法的に正しい!

上記のコードを実行すると、以下のように問題なくコンパイルされる。

defined trait HasFunction
defined trait HasValue
defined class MyClass
myClass: MyClass = ammonite.$sess.cmd3$Helper$MyClass@5e5d3ff7

コード中のコメントにあるようにコメントを削除して、トレイトをインスタンスしようとすると以下のようなエラーが発生する。

cmd5.sc:13: trait HasFunction is abstract; cannot be instantiated
val myTraitFunction = new HasFunction() // 文法エラー! トレイトはインスタンス出来ない。
                      ^cmd5.sc:14: trait HasValue is abstract; cannot be instantiated
val myTraitValue = new HasValue()       // 文法エラー! トレイトはインスタンス出来ない。
                   ^Compilation Faile

Chisel-Bootcampの説明によれば

一般的には、抽象クラスの単一継承による制限の効果を得たい場合に限り抽象クラスを使用し、それ以外の場合はトレイトを使用する

とのことだ。

こちらに使い分けについての指針を書いてくださっている方がいるので参考になるかも。

次はオブジェクトになるのだが、コンパニオンクラスの説明がいい感じに長いのでここで一区切り。 そんなわけで次回はオブジェクトの説明から再開。