I could have stored indices of Polygon in current Scene, index of dragged point in Polygon, and replace it every time. But this approach does not scale - when composition levels go to 5 and further, boilerplate would become unbearable.
Hai assolutamente ragione, questo approccio non scala se non puoi aggirare il boilerplate . Nello specifico, è cambiato lo standard per la creazione di una scena completamente nuova con una piccola parte secondaria. Tuttavia, molti linguaggi funzionali forniscono un costrutto per gestire questo tipo di manipolazione della struttura nidificata: lenti.
Un obiettivo è fondamentalmente un getter e setter per dati immutabili. Un obiettivo ha un focus su una piccola parte di una struttura più grande. Dato un obiettivo, ci sono due cose che puoi fare con esso: puoi visualizzare la piccola parte di un valore della struttura più grande, oppure puoi impostare la piccola parte di un valore di una struttura più grande a un nuovo valore. Ad esempio, supponiamo di avere un obiettivo che si concentra sul terzo elemento di un elenco:
thirdItemLens :: Lens [a] a
Questo tipo indica che la struttura più grande è una lista di cose, e la piccola sottoparte è una di quelle cose. Dato questo obiettivo, puoi visualizzare e impostare il terzo elemento nell'elenco:
> view thirdItemLens [1, 2, 3, 4, 5]
3
> set thirdItemLens 100 [1, 2, 3, 4, 5]
[1, 2, 100, 4, 5]
La ragione per cui gli obiettivi sono utili è perché sono valori che rappresentano getters e setter e puoi astrarli sopra allo stesso modo in cui puoi altri valori. Puoi eseguire funzioni che restituiscono obiettivi, ad esempio una funzione listItemLens
che prende un numero n
e restituisce un obiettivo che visualizza la n
th elemento in un elenco. Inoltre, gli obiettivi possono essere composti :
> 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]]
Ogni obiettivo incapsula il comportamento per attraversare un livello della struttura dei dati. Combinandoli, è possibile eliminare il boilerplate per attraversare più livelli di strutture complesse. Ad esempio, supponendo di avere un scenePolygonLens i
che visualizza il i
th Polygon in una scena e un polygonPointLens n
che visualizza il punto nth
in un poligono, puoi creare un costruttore di lenti per concentrarti solo sullo specifico indica che ti interessa in un'intera scena in questo modo:
scenePointLens i n = lensCompose (polygonPointLens n) (scenePolygonLens i)
Ora supponiamo che un utente faccia clic sul punto 3 del poligono 14 e lo sposti a destra di 10 pixel. Puoi aggiornare la tua scena in questo modo:
lens = scenePointLens 14 3
point = view lens currentScene
newPoint = movePoint 10 0 point
newScene = set lens newPoint currentScene
Questo contiene perfettamente tutto lo standard per attraversare e aggiornare una scena all'interno di lens
, tutto ciò di cui ti devi preoccupare è ciò a cui vuoi cambiare il punto. Puoi astrarre ulteriormente questo con una funzione lensTransform
che accetta un obiettivo, un bersaglio e una funzione per aggiornare la vista del bersaglio attraverso l'obiettivo:
lensTransform lens transformFunc target =
current = view lens target
new = transformFunc current
set lens new target
Questo prende una funzione e la trasforma in un "updater" su una struttura dati complicata, applicando la funzione solo alla vista e usandola per costruire una nuova vista. Quindi torniamo allo scenario di spostare il 3 ° punto del 14 ° poligono a 10 pixel di destra, che può essere espresso in termini di lensTransform
come in questo modo:
lens = scenePointLens 14 3
moveRightTen point = movePoint 10 0 point
newScene = lensTransform lens moveRightTen currentScene
E questo è tutto ciò che serve per aggiornare l'intera scena. Questa è un'idea molto potente e funziona molto bene quando hai alcune funzioni utili per la costruzione di obiettivi che visualizzano i pezzi dei tuoi dati che ti interessano.
Tuttavia questo è tutto abbastanza roba al momento, anche nella comunità di programmazione funzionale. È difficile trovare un buon supporto per le biblioteche per lavorare con gli obiettivi, e ancora più difficile spiegare come funzionano e quali sono i benefici per i tuoi colleghi. Prendi questo approccio con un pizzico di sale.