Я міг би зберігати індекси багатокутника в поточній сцені, індекс перетягнутої точки в полігоні і замінювати його кожного разу. Але такий підхід не має масштабів - коли рівень складу піде на 5 і далі, котельня стане нестерпною.
Ви абсолютно праві, такий підхід не змінюється, якщо ви не можете обійти котельну плиту . Зокрема, змінилася котельня для створення абсолютно нової сцени з крихітним підрозділом. Однак багато функціональних мов створюють конструкцію для вирішення такого роду маніпулювання вкладеною структурою: лінзи.
Лінза - це в основному геттер і сетер для незмінних даних. Лінза має фокус на деякій невеликій частині більшої структури. З огляду на об'єктив, ви можете зробити з ним дві речі - ви можете переглянути невелику частину значення більшої структури, або ви можете встановити невелику частину значення більшої структури на нове значення. Наприклад, припустимо, у вас є об'єктив, який фокусується на третьому елементі списку:
thirdItemLens :: Lens [a] a
Цей тип означає, що більша структура - це список речей, а мала підрозділ - одна з таких речей. З огляду на цей об'єктив, ви можете переглянути та встановити третій елемент у списку:
> view thirdItemLens [1, 2, 3, 4, 5]
3
> set thirdItemLens 100 [1, 2, 3, 4, 5]
[1, 2, 100, 4, 5]
Причина лінз корисна в тому, що вони є значеннями, що представляють геттери та сетери, і ви можете абстрагувати їх так само, як і інші значення. Ви можете робити функції, що повертають лінзи, наприклад listItemLensфункцію, яка приймає число nі повертає об'єктив, переглядаючи цей nелемент у списку. Крім того, лінзи можуть бути складені :
> firstLens = listItemLens 0
> thirdLens = listItemLens 2
> firstOfThirdLens = lensCompose firstLens thirdLens
> view firstOfThirdLens [[1, 2], [3, 4], [5, 6], [7, 8]]
5
> set firstOfThirdLens 100 [[1, 2], [3, 4], [5, 6], [7, 8]]
[[1, 2], [3, 4], [100, 6], [7, 8]]
Кожна лінза інкапсулює поведінку для переходу одного рівня структури даних. Поєднавши їх, ви зможете усунути котельну плиту для переходу декількох рівнів складних конструкцій. Наприклад, якщо припустити, що ви scenePolygonLens iпереглядаєте iполігон у сцені, а polygonPointLens nточку, що оглядає nthточку в полігоні, ви можете зробити конструктор лінз для того, щоб зосередитись саме на певній точці, яка вам важлива у цілій сцені:
scenePointLens i n = lensCompose (polygonPointLens n) (scenePolygonLens i)
Тепер припустимо, що користувач клацає точку 3 багатокутника 14 і переміщує його на 10 пікселів вправо. Ви можете оновити свою сцену так:
lens = scenePointLens 14 3
point = view lens currentScene
newPoint = movePoint 10 0 point
newScene = set lens newPoint currentScene
Це чудово містить всю платівку для проходження та оновлення сцени всередині lens, все, що вам потрібно подбати, - це те, що ви хочете змінити. Ви можете додатково абстрагувати це lensTransformфункцією, яка приймає об'єктив, ціль та функцію оновлення подання цілі через об'єктив:
lensTransform lens transformFunc target =
current = view lens target
new = transformFunc current
set lens new target
Це приймає функцію і перетворює її на "оновлення" складної структури даних, застосовуючи функцію лише до представлення даних та використовуючи її для побудови нового перегляду. Повертаючись до сценарію переміщення 3-ї точки 14-го полігона вправо на 10 пікселів, що може бути виражено lensTransformтак:
lens = scenePointLens 14 3
moveRightTen point = movePoint 10 0 point
newScene = lensTransform lens moveRightTen currentScene
І це все, що потрібно для оновлення всієї сцени. Це дуже потужна ідея і працює дуже добре, коли у вас є кілька приємних функцій для побудови лінз, які переглядають фрагменти ваших даних, які вам цікаві.
Однак зараз це все досить непогано, навіть у спільноті функціональних програм. Важко знайти хорошу бібліотечну підтримку для роботи з лінзами, а ще складніше пояснити, як вони працюють і які переваги приносять вашим колегам. Скористайтеся таким підходом із зерном солі.