Вполне возможно создать свой собственный метод копирования:
abstract class Core(val label: Option[String]) {
def set(label: Option[String]): Core
}
class Impl(label: Option[String] = None) extends Core(label) {
def set(label: Option[String] = this.label) = new Impl(label)
}
используется таким образом:
scala> val i = new Impl
i: Impl = Impl@1930ebb
scala> i.label
res0: Option[String] = None
scala> i.set(label = Some("thing"))
res1: Impl = Impl@b28f30
scala> res1.label
res2: Option[String] = Some(thing)
Но, прагматично, я бы не стал слишком быстро отказываться от использования переменных здесь. Про неизменные значения проще рассуждать, но те, которые вы получаете, достаточно хорошо изолированы внутри синтаксического анализатора, насколько я могу судить. Альтернативной идеей может быть создание метода, который конвертирует все в неизменяемую версию в конце, или если код синтаксического анализатора в конечном итоге хранит все данные где-либо еще, просто оставляя его изменяемым.
Еще один способ - сделать абстрактный класс не абстрактным, а сделать его классом case. Вы можете получить классы из класса case (однако, вывести классы case из классов case нет-нет). Хитрость заключается в том, чтобы сделать ваши переменные данные, которые вам могут понадобиться, для сохранения в поле:
abstract class SpecializedStuff { }
case class ParticularStuff(val i: Int) extends SpecializedStuff
case class Core(details: SpecializedStuff, label: Option[String] = None)
class Impl(p: ParticularStuff) extends Core(p)
scala> val i = new Impl( ParticularStuff(5) )
i: Impl = Core(ParticularStuff(5),None)
scala> i.copy(label = Some("thing"))
res0: Core = Core(ParticularStuff(5),Some(thing))