Я міг би зберігати індекси багатокутника в поточній сцені, індекс перетягнутої точки в полігоні і замінювати його кожного разу. Але такий підхід не має масштабів - коли рівень складу піде на 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
І це все, що потрібно для оновлення всієї сцени. Це дуже потужна ідея і працює дуже добре, коли у вас є кілька приємних функцій для побудови лінз, які переглядають фрагменти ваших даних, які вам цікаві.
Однак зараз це все досить непогано, навіть у спільноті функціональних програм. Важко знайти хорошу бібліотечну підтримку для роботи з лінзами, а ще складніше пояснити, як вони працюють і які переваги приносять вашим колегам. Скористайтеся таким підходом із зерном солі.