読者です 読者をやめる 読者になる 読者になる

無限大な夢のあと

テニスとアニメが大好きな厨二病SEのブログ

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その3(クラス)

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
【クラス】
・クラス定義
気になった部分を以下に記載

まず、最初の点ですが、Scalaでは1クラスに付き、基本的には1つのコンストラクタしか使いません。文法上は複数のコンストラクタを定義できるようになっていますが、実際に使うことはまずないので覚える必要はないでしょう。一応、Scalaでは複数のコンストラクタが定義できるので、この最初の1つのコンストラクタをプライマリコンストラクタとして特別に扱っています。

確かにコップ本では複数のコンストラクタを定義する例は書いてたけど、実際には使うことはまずないのか。
なぜかは置いておき、一旦そういうものだということにしておこう。

プライマリコンストラクタの引数にval/varをつけるとそのフィールドは公開され、外部からアクセスできるようになります。なお、コンストラクタ引数のスコープはクラス定義全体におよびます。

class Point(val x: Int, val y: Int) {
  def +(p: Point): Point = {
    new Point(x + p.x, y + p.y)
  }
  override def toString(): String = "(" + x + ", " + y + ")"
}

上記のような感じでメソッド定義の中から直接コンストラクタ引数を参照できるそうです。

・メソッド定義

先ほど既にメソッド定義の例として+メソッドの定義が出てきましたが、一般的には、

(private[this]/protected[package名]) def メソッド名(引数名1: 引数1の型, 引数名2: 引数2の型, ...): 返り値の型 = {
  本体のコード
}

という形をとります。ちなみに、メソッド本体が1つの式だけからなる場合は、

(private[this]/protected[package名]) def メソッド名(引数名1: 引数1の型, 引数名2: 引数2の型, ...): 返り値の型 = 本体のコード

と書けます(実際には、こちらの方が基本形で、= {}を使ったスタイルの方が{}内に複数の式を並べて書けることを利用した派生形になりますが、前者のパターンを使うことが多いでしょう)。
返り値の型は省略しても特別な場合以外型推論してくれますが、読みやすさのために、返り値の型は明記する習慣を付けるようにしましょう。
また、privateを付けるとそのクラス内だけから、protectedを付けるとそのクラスの派生クラスからのみアクセスできるフィールドになります。さらに、 private[this] をつけると、同じオブジェクトからのみ、 protected[パッケージ名] をつけると、追加で同じパッケージに所属しているもの全てからアクセスできるようになります。privateもprotectedも付けない場合、そのフィールドはpublicとみなされます。

private[this]という定義方法があるのか。
返り値の明示的に書くということは、いくら型推論してくれるからとはいえ、確かに心がけたいところ。

複数の引数リストを持つメソッドには、Scalaの糖衣構文と組み合わせて流暢なAPIを作ったり、後述するimplicit parameterのために必要になったり、型推論を補助するために使われたりといった用途があります

implicit parameterは名前だけ聞いたことあるが、まだわからん。
デフォルト引数のようなものかな。

何はともあれ、複数の引数リストを持つ加算メソッドを定義してみましょう。

scala> class Adder {
     |   def add(x: Int)(y: Int): Int = x + y
     | }
defined class Adder

scala> val adder = new Adder()
adder: Adder = Adder@7263d8eb

scala> adder.add(2)(3)
res1: Int = 5

scala> adder.add(2) _
res2: Int => Int = <function1>

複数の引数リストを持つメソッドはobj.m(x, y)の形式でなくobj.m(x)(y)の形式で呼びだすことになります。また、一番下の例のように最初の引数だけを適用して新しい関数を作る(部分適用)こともできます。

obj.m(x, y)の形式でなくobj.m(x)(y)の形式で呼びだすのかー。
JavaScriptで学ぶ関数型言語の時に、undersocre.jsでそのような指定の仕方があったような気がする、ちょい違和感ある。
引数を一つだけ渡して、部分適用するのは自分の頭の中では違和感なし。

・フィールド定義
特に言及するポイントなし。

・継承

クラスのもう1つの機能は、継承です。継承には2つの目的があります。 1つは継承によりスーパークラスの実装をサブクラスでも使うことで実装を再利用することです。もう1つは複数のサブクラスが共通のスーパークラスのインタフェースを継承することで処理を共通化することです1。

実装の継承には複数の継承によりメソッドやフィールドの名前が衝突する場合の振舞いなどに問題があることが知られており、Javaでは実装継承が1つだけに限定されています。 Java 8ではインタフェースにデフォルトの実装を持たせられるようになりましたが、変数は持たせられないなどの制約があります。 Scalaではトレイトという仕組みで複数の実装の継承を実現していますが、トレイトについては別の節で説明します。

1. このように継承などにより型に親子関係を作り、複数の型に共通のインタフェースを持たせることをサブタイピング・ポリモーフィズムと呼びます。Scalaでは他にも構造的部分型というサブタイピング・ポリモーフィズムの機能がありますが、実際に使われることが少ないため、このテキストでは説明を省略しています

多重継承問題を取り上げていて、良い資料だなと感じました。

基本的に、継承のはたらきはJavaのクラスと同じですが、既存のメソッドをオーバーライドするときはoverrideキーワードを使わなければならない点が異なります。たとえば、

scala> class APrinter() {
     |   def print(): Unit = {
     |     println("A")
     |   }
     | }
defined class APrinter

scala> class BPrinter() extends APrinter {
     |   override def print(): Unit = {
     |     println("B")
     |   }
     | }
defined class BPrinter

scala> new APrinter().print
A

scala> new BPrinter().print
B

のようにすることができます。ここでoverrideキーワードをはずすと、

scala> class BPrinter() extends APrinter {
     |   def print(): Unit = {
     |     println("B")
     |   }
     | }
<console>:14: error: overriding method print in class APrinter of type ()Unit;
 method print needs `override' modifier
         def print(): Unit = {
             ^

のようにメッセージを出力して、コンパイルエラーになります。Javaではしばしば、気付かずに既存のメソッドをオーバーライドするつもりで新しいメソッドを定義してしまうというミスがありましたが、Scalaではoverrideキーワードを使って言語レベルでこの問題に対処しているのです。

継承時にメソッドをoverrideする時はJavaでやるようなoverrideアノテーションではなく、言語機能としてoverrideキーワードを使用するそうです。
overrideキーワードを使わないとコンパイルエラーになるなんて、良い仕様だな。

ちなみに、Goはクラスではなく、すべてInterfaceや構造体で継承のようなことをやるので、今回は全体的に記述なし。


Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門