Intereting Posts

Могу ли я обновить глубоко вложенный неизменный объект, не сообщая ему о его контексте?

Представим себе, что у меня есть вложенный неизменный объектный граф, вдоль этих строк (используя синтаксис Kotlin, но, надеюсь, это понятно):

data class House(val bedroom: Bedroom, val bathroom: Bathroom, val kitchen: Kitchen) class Bedroom class Bathroom data class Kitchen(val oven: Oven, val kettle: Kettle) class Oven data class Kettle(val on: Boolean) var house = House(Bedroom(), Bathroom(), Kitchen(Oven(), Kettle(false))) 

Теперь я хочу включить чайник. Если бы объекты были изменчивыми, я бы просто написал:

 data class Kettle(var on: Boolean) { fun activate() { this.on = true } } house.kitchen.kettle.activate() 

Но поскольку они неизменны, я должен написать:

 data class Kettle(val on: Boolean) { fun activate(house: House): House { return house.copy(kitchen = kitchen.copy(kettle = kettle.copy(on = true))) } } house = house.kitchen.kettle.activate(house) 

(На самом деле это немного сложнее, но этот псевдокод будет делать).

Мне это не нравится, не потому, что это давно, само по себе, а потому, что Чайник теперь должен знать не только о собственном внутреннем состоянии, но и о полном контексте, в котором он существует.

Как я могу переписать это так, чтобы каждый объект мог нести ответственность за предоставление своей собственной логики мутации, не зная о полном графе объектов? Или я просто пытаюсь выйти замуж за объектно-ориентированные и функциональные концепции невозможным способом?

Solutions Collecting From Web of "Могу ли я обновить глубоко вложенный неизменный объект, не сообщая ему о его контексте?"

Один из возможных подходов, о которых я думал (кстати, это вопрос, кстати):

 data class Kettle(val on: Boolean) { fun activate() { return Transform(this, Kettle(on = true)) } } class Transform<T>(val what: T, val replacement: T) { fun <U> apply(x: U): U { if (x is T && x == what) { return replacement as U } else { return x } } } 

Идея здесь заключается в том, что преобразование – это функция, которую вы можете применить к каждому объекту в графе, и это изменит только то, what вы сказали ему изменить.

Таким образом, вы используете его следующим образом:

 val transform = house.kitchen.kettle.activate() house = house.transformEverything(transform) 

Там, где каждый класс имеет такую ​​реализацию:

 data class House(val bedroom: Bedroom, val bathroom: Bathroom, val kitchen: Kitchen) { fun transformEverything(transform: Transform): House { return transform(this).copy( bedroom = bedroom.transformEverything(transform), bathroom = bathroom.transformEverything(transform), kitchen = kitchen.transformEverything(transform) ) } } 

Что рекурсивно дает transform возможность изменить каждый объект, который он хочет, и он будет применять его только к одному.

Этот подход плохой, потому что:

  1. Тонны котельной плиты, дающие все свои собственные немые версии метода transformEverything
  2. Кажется странным (и неэффективным), что нужно вызвать функцию для каждого объекта на графике, чтобы изменить ее.

Но он достигает моей цели Чайника, не нуждающегося в том, чтобы знать что-либо о его контексте, и довольно просто написать функцию activate . Мысли?

Именно там функциональные линзы показывают свою силу. Например, используя поэтикс / klenses ,

 val kettleLens = +House::kitchen + Kitchen::kettle var house = House(...) house = kettleLens(house) { copy(on = true) }