こういうツイートを見た。
Scalaのフィールド、class名のすぐ後ろのかっこ内に書くのとbodyに書くの何がちがうん
— Neo (@GlassesNeo) 2024年11月24日
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
*1:まさにconstructしてるね