Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scalaではクラスのコンストラクタ引数とフィールドを同時に定義できる

こういうツイートを見た。

Scalaのフィールド、class名のすぐ後ろのかっこ内に書くのとbodyに書くの何がちがうん

つまり、この二者は何が違うのかということだ:

class C1(val field: Int)

class C2:
  val field: Int

この疑問を解くには、クラスのコンストラクタとフィールドとの関係、そして両者をつなぐ便利な記法について知るとよい。

実行環境

この記事では、Scala 3.5.2を利用している(が、大抵のバージョンでも同様)。

また、この記事では紙面節約のためにoptional brace syntaxで書かれている。つまり、以下の記述は等しい:

class Foo {
  // ...
}
class Foo:
  // ...

Scalaのクラス

Scalaはオブジェクト指向言語でもあるので、クラスを作ることができる:

class Coke

コンストラクタ

クラスなので、コンストラクタ引数を取ることができる:

class Coke(isDietCoke: Boolean):
  // ここでコンストラクタ引数を使った初期化処理を書く
  println(s"初期化中: ${isDietCoke}")

Coke(true) // 初期化中: true と表示される

いちおう説明しておくと、コンストラクタとはざっくり言うとクラスを作るときに呼ぶやつだ*1:

Coke(true) ←ここがコンストラクタ

Scalaではクラスを作るときにnewを書かなくて良いので本当に関数とかメソッドに見えるね。

フィールド

クラスなので、インスタンスにフィールドを作ることができる:

class Coke:
  val isDietCoke: Boolean = false

Coke().isDietCoke // => false

フィールドのための特別な記法はなくて、普通にvalを置くだけだ。

コンストラクタ引数をそのままフィールドにしたい

ところで、コンストラクタ引数をそのまま手を加えずにフィールドにしたいことがよくある:

class Coke(isDietCokeArg: Boolean):
  val isDietCoke = isDietCokeArg

Scalaはこれに便利な記法を与えている。コンストラクタ引数にvalをつけて宣言すると、それはそのままフィールドになる:

class Coke(val isDietCoke: Boolean)

Coke(true).isDietCoke // => true

まとめると

ここで最初の問いに戻ってくる。

Scalaのフィールド、class名のすぐ後ろのかっこ内に書くのとbodyに書くの何がちがうん

  • class名のすぐ後ろのかっこにvalを書くと、コンストラクタから直接設定できるフィールドになる
  • bodyにvalを書くと、ふつうのフィールドになる

ここで、値が固定されている場合は同じような挙動になる:

class BoxA(val inner: Int = 42)
class BoxB:
  val inner: Int = 42

ただし、前者はあくまでもクラスのコンストラクタにデフォルトの値を与えている、という建て付けになる。このため前者では作成時に値を注入できる:

BoxA(0) // これができる
BoxB(0) // コンストラクタにパラメータがないのでそれはできない

ただ値を固定したい場合は作成時に値を注入したいわけではないだろうから、普通にbodyでvalと書くほうが意図通りだと思う。

Further Reading

docs.scala-lang.org

*1:まさにconstructしてるね

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?