きっかけはJson4sの、このissue.
テストクラスの中で定義したcase classが正しく判定されない、というもの。
いくらJson4sのコードを追いかけてもよく分からなかったところ、いつも色々なことを教えて頂くKenji Yoshidaさん(@xuwei_k)に教えて頂きました。
outerのインスタンスは、コンストラクタの引数ゼロの前提でjson4sが作っていたはずです。
— Kenji Yoshida (@xuwei_k) 2019年6月18日
(勝手にスコープにあるものを引っ張ってきたりは、難しいので?してない)
(JavaでもScalaでも)inner生成するのにあたってouterを引数にとる必要があるはずなのでhttps://t.co/loMLMUGkHI
更に!普通にJson4sのREADMEにも書かれていました…最初から読めよっていう…
For classes defined in a trait it's a bit difficult to get to their companion object, which is needed to provide default values. We could punt on those but that brings us to the next problem, that the compiler generates an extra field in the constructor of such case classes. The first field in the constructor of those case classes is called $outer and is of type of the defining trait. So somehow we need to get an instance of that object, naively we could scan all classes and collect the ones that are implementing the trait, but when there are more than one: which one to take?
ネストしたcase classのコンストラクタの場合、Scalaのコンパイラが$outerというフィールド名にouter classを入れておいてくれる! Json4sは全てのフィールドのインスタンスを作ってから、case classのインスタンスを作るので、この際にouter classが何も引数を取らないクラスであれば生成できる、というわけです(引数が必要なクラスだと生成に失敗します)。
Json4sでは、そのようなパターンにも対応できるようにouter classのインスタンスを渡す方法が用意されています。詳しくは先ほどのREADMEを参照して下さい。
この、Scalaではinner classのインスタンスがouter classのインスタンスに依存するのは、Scalaの経路依存型、という機能の話になり、以下のサイトが参考になりました。