Problem z funargiem - Funarg problem

W informatyce The Problem funarg (problem argumentem funkcji) odnosi się do trudności we wdrażaniu FUNKCJE pierwszej klasy ( funkcje jak obiekty pierwszej klasy ) w programowaniu implementacje języka, tak by zużywało na stosie alokacji pamięci funkcji.

Trudność pojawia się tylko wtedy, gdy treść zagnieżdżonej funkcji odnosi się bezpośrednio (tj. nie przez przekazywanie argumentów) do identyfikatorów zdefiniowanych w środowisku, w którym funkcja jest zdefiniowana, ale nie w środowisku wywołania funkcji. Standardowym rozwiązaniem jest albo zakazanie takich odniesień, albo tworzenie zamknięć .

Istnieją dwie subtelnie różne wersje problemu funarga. Problem w górę funarg wynika z zwracania (lub w inny sposób przesyłania "w górę") funkcji z wywołania funkcji. Problem w dół funarg wynika z przekazania funkcji jako parametru do innego wywołania funkcji.

Problem w górę funargu

Kiedy jedna funkcja wywołuje inną podczas wykonywania typowego programu, lokalny stan wywołującego (w tym parametry i zmienne lokalne ) musi zostać zachowany, aby wykonanie było kontynuowane po powrocie wywoływanego. W większości skompilowanych programów ten stan lokalny jest przechowywany na stosie wywołań w strukturze danych zwanej ramką stosu lub rekordem aktywacji . Ta ramka stosu jest odkładana lub alokowana jako wstęp do wywołania innej funkcji i jest usuwana lub zwalniana, gdy inna funkcja powraca do funkcji, która wykonała wywołanie. Problem w górę funarg pojawia się, gdy funkcja wywołująca odwołuje się do stanu wywołanej/wychodzącej funkcji po zwróceniu tej funkcji. W związku z tym ramka stosu zawierająca zmienne stanu wywoływanej funkcji nie może być zwalniana, gdy funkcja zwraca, naruszając paradygmat wywołania funkcji opartej na stosie .

Jednym z rozwiązań rosnącego problemu funarg jest po prostu alokowanie wszystkich rekordów aktywacji ze sterty zamiast stosu i poleganie na jakiejś formie zbierania śmieci lub zliczania odwołań w celu ich cofnięcia, gdy nie są już potrzebne. Zarządzanie rekordami aktywacji na stercie było w przeszłości postrzegane jako mniej wydajne niż na stosie (chociaż jest to częściowo sprzeczne) i było postrzegane jako narzucające znaczną złożoność implementacji. Większość funkcji w typowych programach (mniej w przypadku programów w funkcjonalnych językach programowania ) nie tworzy funargów w górę, co zwiększa obawy o potencjalny narzut związany z ich implementacją. Co więcej, takie podejście jest naprawdę trudne w językach, które nie obsługują zbierania śmieci.

Niektóre kompilatory zorientowane na wydajność stosują podejście hybrydowe, w którym rekordy aktywacji funkcji są alokowane ze stosu, jeśli kompilator jest w stanie wywnioskować, poprzez analizę programu statycznego , że funkcja nie tworzy żadnych przyrostów. W przeciwnym razie rekordy aktywacji są przydzielane ze sterty.

Innym rozwiązaniem jest po prostu skopiowanie wartości zmiennych do zamknięcia w momencie tworzenia zamknięcia. Spowoduje to inne zachowanie w przypadku zmiennych mutowalnych, ponieważ stan nie będzie już dzielony między domknięciami. Ale jeśli wiadomo, że zmienne są stałe, to takie podejście będzie równoważne. W ML języki takie podejście, ponieważ zmienne w tych językach są zobowiązane do wartości tj zmiennych nie mogą być zmieniane. Java stosuje to podejście również w odniesieniu do klas anonimowych, ponieważ pozwala tylko na odwoływanie się do zmiennych w otaczającym zakresie, które są final(tj. stałe).

Niektóre języki pozwalają programiście na jawny wybór między tymi dwoma zachowaniami. Anonimowe funkcje PHP 5.3 wymagają określenia, które zmienne należy uwzględnić w zamknięciu za pomocą use ()klauzuli; jeśli zmienna jest wymieniona przez odwołanie, zawiera odwołanie do oryginalnej zmiennej; w przeciwnym razie przekazuje wartość. W anonimowych funkcjach Apple Blocks przechwycone zmienne lokalne są domyślnie przechwytywane przez wartość; jeśli chce się dzielić stan między domknięciami lub między domknięciem i zewnętrznym zakresem, zmienna musi być zadeklarowana z __blockmodyfikatorem, w którym to przypadku zmienna jest alokowana na stercie.

Przykład

Poniższy pseudokod podobny do Haskella definiuje kompozycję funkcji :

compose f g = λx → f (g x)

λjest operatorem do konstruowania nowej funkcji, która w tym przypadku ma jeden argument xi zwraca wynik pierwszego zastosowania gdo x, a następnie zastosowania fdo tego. Ta funkcja λ przenosi funkcje fi g(lub wskaźniki do nich) jako stan wewnętrzny.

Problem w tym przypadku występuje, jeśli funkcja compose przydziela zmienne parametrów fi gna stosie. Gdy composezwraca, ramka stosu zawierająca fi gjest odrzucana. Gdy funkcja wewnętrzna λxpróbuje uzyskać dostęp do g, uzyska dostęp do odrzuconego obszaru pamięci.

Problem w dół funarg

Funarg w dół może również odnosić się do stanu funkcji, gdy ta funkcja w rzeczywistości nie jest wykonywana. Jednakże, ponieważ z definicji istnienie funargu w dół jest zawarte w wykonaniu funkcji, która go tworzy, ramka stosu dla funkcji może zwykle nadal być przechowywana na stosie. Niemniej jednak istnienie funargów skierowanych w dół implikuje strukturę drzewa zamknięć i ramek stosu, które mogą skomplikować ludzkie i maszynowe rozumowanie dotyczące stanu programu.

Problem funarga w dół komplikuje wydajną kompilację wywołań ogona i kodu napisanego w stylu z przekazywaniem kontynuacji . W tych szczególnych przypadkach intencją programisty jest (zazwyczaj), aby funkcja działała w ograniczonej przestrzeni stosu, więc „szybsze” zachowanie może być w rzeczywistości niepożądane.

praktyczne implikacje

Historycznie rzecz biorąc, problem rosnącego funargu okazał się być trudniejszy. Na przykład język programowania Pascal umożliwia przekazywanie funkcji jako argumentów, ale nie zwracanie ich jako wyników; zatem implementacje Pascala są wymagane do rozwiązania problemu funarga w dół, ale nie w górę. W Modula-2 i Oberon języki programowania (potomstwo Pascala) umożliwiają zarówno jako funkcji parametrów i wartości powrotu, a przyporządkowana funkcja może być zagnieżdżona funkcję. Język programowania C historycznie unika głównej trudności związanej z problemem funarg, nie pozwalając na zagnieżdżanie definicji funkcji; ponieważ środowisko każdej funkcji jest takie samo, zawiera tylko statycznie przydzielone zmienne globalne i funkcje, wskaźnik do kodu funkcji całkowicie opisuje funkcję. Firma Apple zaproponowała i zaimplementowała składnię zamykania dla języka C, która rozwiązuje problem rosnącego funargu poprzez dynamiczne przenoszenie zamknięć ze stosu do sterty w razie potrzeby. Język programowania Java radzi sobie z tym, wymagając, aby kontekst używany przez funkcje zagnieżdżone w anonimowych klasach wewnętrznych i lokalnych był zadeklarowany jako final , a kontekst używany przez wyrażenia lambda był faktycznie finalny. C# i D mają lambdy (domknięcia), które hermetyzują wskaźnik funkcji i powiązane zmienne.

W językach funkcjonalnych funkcje są wartościami pierwszej klasy, które można przekazać w dowolnym miejscu. W związku z tym, implementacje Scheme lub Standard ML muszą rozwiązywać zarówno problemy związane z grzybami w górę, jak i w dół. Jest to zwykle realizowane przez reprezentowanie wartości funkcji jako zamknięć przydzielonych na stercie , jak opisano wcześniej. OCaml kompilator zatrudnia hybrydowy techniki (na podstawie analizy programu ) w celu zmaksymalizowania wydajności.

Zobacz też

Bibliografia

Zewnętrzne linki