Использование Lens в классе без регистра, расширяющем что-то с помощью конструктора в Scala

Я, вероятно, думаю об этом неправильно, но у меня возникают проблемы в Scala с использованием линз в классах, расширяющих что-то с помощью конструктора.

class A(c: Config) extends B(c) {
    val x: String = doSomeProcessing(c, y) // y comes from B
}

Я пытаюсь создать Lens для изменения этого класса, но у меня проблемы с этим. Вот что я хотел бы уметь делать:

val l = Lens(
    get = (_: A).x,
    set = (c: A, xx: String) => c.copy(x = xx) // doesn't work because not a case class
)

Я думаю, что все сводится к тому, чтобы найти хороший способ мутировать этот класс.

Каковы мои варианты достижения чего-то подобного? Я думал об этом двумя способами:

  • Переместите логику инициализации в объект-компаньон A в def apply(c: Config) и измените класс A на case class, который создается из объекта-компаньона. К сожалению, я не могу расширить B(c) в своем object, потому что у меня есть доступ только к c в его методе apply.
  • Сделайте x var. Затем в Lens.set просто A.clone затем установите значение x, затем верните клонированный экземпляр. Это, вероятно, сработает, но выглядит довольно уродливо, не говоря уже о том, что изменение этого числа на var может вызвать удивление.
  • Используйте немного магии отражения, чтобы сделать копию. Не совсем поклонник этого подхода, если я могу избежать его.

Что вы думаете? Я думаю об этом действительно неправильно, или есть простое решение этой проблемы?


person Charles Menguy    schedule 28.03.2017    source источник


Ответы (1)


Это зависит от того, что вы ожидаете от своего Lens. Законы Lens определяют, что сеттер должен заменить значение, которое получит геттер, сохраняя при этом все остальное без изменений. Непонятно, что имеется в виду под всем остальным здесь.

Вы хотите, чтобы конструктор для B вызывался при установке? У вас есть метод doSomeProcessing?

Если все ваши методы чисто функциональные, то вы можете считать, что класс A имеет два "поля", c: Config и x: String, так что вы можете заменить его на case class с этими полями. Однако это вызовет проблему при попытке реализовать конструктор только с c в качестве параметра.

Я бы рассмотрел следующее:

class A(val c: Config) extends B(c) {
  val x = doSomeProcessing(c, y)
  def copy(newX: String) = new A(c) { override val x = newX }
}

Написанный вами Lens теперь полностью действителен (за исключением именованного параметра в методе copy).

Будьте осторожны, если у вас есть другие свойства в A, которые зависят от x, это может создать экземпляр с неожиданными значениями для них.

Если вы не хотите, чтобы c было свойством класса A, вы не сможете его клонировать или пересобрать экземпляр, не предоставив Config вашему компоновщику, которого не может иметь компоновщик Lenses, поэтому, похоже, ваша цель быть недостижимым.

person Cyrille Corpet    schedule 28.03.2017