Naiwne implementacje rzeczywiście ujawniłyby ten problem - kiedy tworzysz nową strukturę danych zamiast aktualizować istniejącą w miejscu, musisz mieć narzut.
Różne języki mają różne sposoby radzenia sobie z tym, a większość z nich używa kilku sztuczek.
Jedną ze strategii jest zbieranie śmieci . W momencie, gdy nowa struktura została utworzona, lub wkrótce potem, odniesienia do starej struktury wykraczają poza zakres, a śmieciarz odbierze ją natychmiast lub wystarczająco szybko, w zależności od algorytmu GC. Oznacza to, że chociaż narzut jest nadal narzutem, jest to tylko tymczasowe i nie będzie rosnąć liniowo wraz z ilością danych.
Kolejnym jest wybieranie różnych rodzajów struktur danych. Tam, gdzie tablice są podstawową strukturą danych listy w imperatywnych językach (zwykle zapakowane w pewnego rodzaju kontener dynamicznej realokacji, taki jak std::vector
w C ++), języki funkcjonalne często preferują listy połączone. W przypadku listy połączonej operacja poprzedzająca („wady”) może ponownie wykorzystać istniejącą listę jako ogon nowej listy, więc wszystko, co naprawdę zostanie przydzielone, to nowa głowa listy. Podobne strategie istnieją dla innych typów struktur danych - zestawów, drzew, nazywacie to.
A potem leniwa ocena à la Haskell. Chodzi o to, że tworzone struktury danych nie są w pełni tworzone natychmiast; zamiast tego są one przechowywane jako „thunks” (można je traktować jako przepisy na konstruowanie wartości, gdy jest to potrzebne). Tylko wtedy, gdy wartość jest potrzebna, thunk zostaje powiększony do wartości rzeczywistej. Oznacza to, że alokacja pamięci może zostać odroczona do czasu, gdy ocena będzie konieczna, i w tym momencie kilka łączników może być połączonych w jedną alokację pamięci.