Czy istnieje funkcjonalny język, który pozwala stosować semantykę stosu - automatyczne deterministyczne zniszczenie na końcu zakresu?
Czy istnieje funkcjonalny język, który pozwala stosować semantykę stosu - automatyczne deterministyczne zniszczenie na końcu zakresu?
Odpowiedzi:
Nie o tym wiem, choć nie jestem ekspertem od programowania funkcjonalnego.
Zasadniczo wydaje się to dość trudne, ponieważ wartości zwracane z funkcji mogą zawierać odwołania do innych wartości, które zostały utworzone (na stosie) w ramach tej samej funkcji, lub równie łatwo mogły zostać przekazane jako parametr lub do których odwołuje się coś przekazanego jako parametr. W C problem ten rozwiązuje się, pozwalając, aby zwisające wskaźniki (a ściślej niezdefiniowane zachowanie) mogły wystąpić, jeśli programista nie naprawi problemu. To nie jest rozwiązanie, które akceptują funkcjonalni projektanci języków.
Istnieją jednak potencjalne rozwiązania. Jednym z pomysłów jest uczynienie okresu istnienia wartości częścią typu wartości, wraz z odniesieniami do niej, oraz zdefiniowanie reguł opartych na typach, które zapobiegną zwracaniu wartości przypisanych do stosu lub odwoływaniu się do czegoś zwróconemu z funkcjonować. Nie zastanawiałem się nad implikacjami, ale podejrzewam, że byłoby to okropne.
W przypadku kodu monadycznego istnieje inne rozwiązanie, które jest (właściwie lub prawie) monadyczne i może dać rodzaj automatycznie deterministycznie zniszczonej IORef. Zasadą jest zdefiniowanie działań „zagnieżdżających”. W połączeniu (za pomocą operatora asocjacyjnego) definiują one przepływ kontrolny zagnieżdżania - myślę, że „element XML”, z skrajnie lewymi wartościami zapewniającymi zewnętrzną parę tagów początkowych i końcowych. Te „znaczniki XML” definiują po prostu kolejność monadycznych działań na innym poziomie abstrakcji.
W pewnym momencie (po prawej stronie łańcucha kompozycji asocjacyjnej) potrzebujesz pewnego rodzaju terminatora, aby zakończyć zagnieżdżanie - czegoś, co wypełni dziurę w środku. Potrzeba terminatora jest tym, co prawdopodobnie oznacza, że operator kompozycji zagnieżdżonej nie jest monadyczny, chociaż znowu, nie jestem do końca pewien, ponieważ nie przepracowałem szczegółów. Ponieważ wszystko, co robi terminator, polega na przekształceniu operacji zagnieżdżania w skutecznie skomponowaną normalną akcję monadyczną, a może nie - niekoniecznie wpływa to na operator kompozycji zagnieżdżania.
Wiele z tych akcji specjalnych miałoby zerowy krok „znacznika końcowego” i utożsamiałby krok „znacznika początku” z prostą akcją monadyczną. Ale niektóre reprezentowałyby deklaracje zmienne. Reprezentowałyby one konstruktor ze znacznikiem początkowym, a destruktor ze znacznikiem końcowym. Więc dostajesz coś takiego ...
act = terminate ((def-var "hello" ) >>>= \h ->
(def-var " world") >>>= \w ->
(use-val ((get h) ++ (get w)))
)
Przekładając na kompozycję monadyczną o następującej kolejności wykonania, każdy tag (nie element) staje się normalną akcją monadyczną ...
<def-var val="hello"> -- construction
<def-var val=" world> -- construction
<use-val ...>
<terminator/>
</use-val> -- do nothing
</def-val> -- destruction
</def-val> -- destruction
Takie reguły mogą pozwolić na implementację RAII w stylu C ++. Odwołania podobne do IORef nie mogą uciec przed swoim zasięgiem z podobnych powodów, dla których normalne IORef nie mogą uciec od monady - reguły składu asocjacyjnego nie pozwalają na ucieczkę odniesienia.
EDYCJA - prawie zapomniałem powiedzieć - jest określony obszar, którego nie jestem pewien. Ważne jest, aby upewnić się, że zmienna zewnętrzna nie może odwoływać się do zmiennej wewnętrznej, w zasadzie, więc muszą istnieć ograniczenia, co możesz zrobić z tymi odwołaniami podobnymi do IORef. Znów nie przepracowałem wszystkich szczegółów.
Dlatego konstrukcja może np. Otworzyć plik, który zniszczenie zamyka. Konstrukcja może otworzyć gniazdo, które zamyka zniszczenie. Zasadniczo, podobnie jak w C ++, zmienne stają się menedżerami zasobów. Ale w przeciwieństwie do C ++, nie ma obiektów przydzielanych do sterty, których nie można automatycznie zniszczyć.
Mimo że ta struktura obsługuje RAII, nadal potrzebujesz śmietnika. Chociaż akcja zagnieżdżania może przydzielić i zwolnić pamięć, traktując ją jako zasób, wciąż istnieją wszystkie odniesienia do (potencjalnie współdzielonych) wartości funkcjonalnych w tej części pamięci i gdzie indziej. Biorąc pod uwagę, że pamięć może być po prostu przydzielona na stosie, unikając konieczności korzystania ze sterty, rzeczywiste znaczenie (jeśli istnieje) ma znaczenie dla innych rodzajów zarządzania zasobami.
Osiąga to oddzielenie zarządzania zasobami w stylu RAII od zarządzania pamięcią, przynajmniej w przypadku, gdy RAII opiera się na prostym zakresie zagnieżdżania. Nadal potrzebujesz śmietnika do zarządzania pamięcią, ale dostajesz bezpieczne i terminowe automatyczne deterministyczne oczyszczanie innych zasobów.
shared_ptr<>
), nadal zachowuje się deterministyczne zniszczenie. Jedyną trudną rzeczą dla RAII są odwołania cykliczne; RAII działa czysto, jeśli graf własności jest Directed Acyclic Graph.
Jeśli uważasz C ++ za język funkcjonalny (ma lambdas), to jest to przykład języka, który nie korzysta ze śmiecia.
Muszę powiedzieć, że pytanie jest nieco źle zdefiniowane, ponieważ zakłada, że istnieje standardowa kolekcja „języków funkcjonalnych”. Prawie każdy język programowania obsługuje pewną ilość programowania funkcjonalnego. I prawie każdy język programowania obsługuje pewną liczbę programów imperatywnych. Gdzie można wytyczyć linię, która mówi, który jest językiem funkcjonalnym, a który jest językiem imperatywnym, innym niż kierowanie się uprzedzeniami kulturowymi i popularnym dogmatem?
Lepszym sposobem sformułowania pytania byłoby „czy możliwe jest wsparcie programowania funkcjonalnego w pamięci przydzielonej na stosie”. Odpowiedź jest, jak już wspomniano, bardzo trudna. Funkcjonalny styl programowania promuje alokację struktur danych rekurencyjnych do woli, co wymaga pamięci sterty (niezależnie od tego, czy śmieci są gromadzone, czy liczone referencje). Istnieje jednak dość wyrafinowana technika analizy kompilatora zwana analizą pamięci opartą na regionie, za pomocą której kompilator może dzielić stertę na duże bloki, które można alokować i zwalniać automatycznie, w sposób podobny do alokacji stosu. Strona Wikipedii wymienia różne implementacje tej techniki, zarówno dla języków „funkcjonalnych”, jak i „imperatywnych”.