Funkcje pierwszej klasy

11

Zacząłem poważnie przyglądać się Lispowi w ten weekend (tzn. Uczyłem się tylko Lisp i nie wracam do projektów w C #) i muszę powiedzieć, że to uwielbiam. Zajmowałem się innymi językami funkcjonalnymi (F #, Haskell, Erlang), ale nie czułem remisu, który dał mi Lisp.

Teraz, gdy kontynuuję naukę Lisp, zacząłem się zastanawiać, dlaczego języki niefunkcjonalne nie obsługują funkcji najwyższej klasy. Wiem, że języki takie jak C # mogą robić podobne rzeczy z delegatami i do tego stopnia, że ​​możesz używać wskaźników do funkcji w C / C ++, ale czy istnieje powód, dla którego nigdy nie stałoby się to funkcją w tych językach? Czy jest wadą uczynienie funkcji pierwszorzędnymi? Jest to dla mnie niezwykle przydatne, więc nie rozumiem, dlaczego więcej języków (poza paradygmatem funkcjonalnym) go nie implementuje.

[Edytuj] Doceniam dotychczasowe odpowiedzi. Ponieważ pokazano mi, że wiele języków obsługuje teraz funkcje najwyższej klasy, ponownie sformułuję pytanie, aby wyjaśnić, dlaczego ich wdrożenie zajmuje tyle czasu? [/Edytować]

Jetti
źródło
3
Nie zgadzam się, że C # może robić „podobne rzeczy”. Powiedziałbym, że ma pierwszorzędne funkcje do wszystkich celów i celów (co rujnuje twoją przesłankę). Ma składnię literałów funkcyjnych, możesz
upychać
@Logan - zastanawiałem się, czy w tym przykłady C #, ale zdecydowałem się oprzeć na tym wpisie w Wikipedii . Zgodnie z wpisem nie ma prawdziwych funkcji pierwszej klasy, ponieważ „ponieważ obiekty utrzymujące stan dynamiczny funkcji muszą być konstruowane ręcznie”. Jeśli jednak jest to niepoprawne stwierdzenie, chciałbym wiedzieć, dlaczego! (W końcu jestem tutaj, aby się uczyć i nie utknąć na moich drogach!)
Jetti
2
ten wpis na Wikipedii jest nieaktualny. Nowoczesne C # zapewnia właściwą lambda.
SK-logic
Myślę, że ten akapit jest źle napisany. Wydaje się, że mówi, że funktory C ++ nie są pierwszej klasy, podobnie jak delegaci C #? Chociaż mogę zgodzić się z argumentem funktorów, nie jestem pewien, czy zgodziłbym się na delegatów C #. Mówi także „(patrz podnoszenie lambda)”, co jest stwierdzeniem o tych językach obsługujących zamykanie leksykalne, a nie „funkcjonuje jako wartości”. Możesz mieć jedno bez drugiego. Nawet jeśli tak jest, nie jest to prawdą w języku C #, ale ma leksykalne zamknięcia. Część problemu polega na tym, że „funkcja pierwszej klasy” może być niejasnym terminem.
Logan Capaldo
1
@Logan & SK-Logic - Doceniam twoją opinię i pozostanie konstruktywnym. Wciąż się uczę i bardzo miło jest nie płonąć, więc doceniam to. Bardzo dziękuję za komentarze i odpowiedzi!
Jetti

Odpowiedzi:

5

C #, VB.NET, Python, JavaScript, teraz nawet C ++ 0x zapewnia funkcje najwyższej klasy.

Aktualizacja:

Wdrożenie zamknięć wymaga podnoszenia lambda - techniki pierwotnie zupełnie obcej dla programistów. Prawidłowe funkcje pierwszej klasy (w tym zamknięcia) wymagają co najmniej wsparcia ze strony środowiska wykonawczego i maszyny wirtualnej. Trochę czasu zajęło także zaadaptowanie starych technik funkcjonalnych do imperatywnego świata.

I najważniejsza część - funkcje pierwszej klasy są ledwo użyteczne, jeśli nie ma śmieci. Został wprowadzony do masowego programowania dopiero niedawno, z Javą, a tym samym z .NET. Oryginalne podejście Java polegało na nadmiernym uproszczeniu języka, tak aby był on zrozumiały dla przeciętnych programistów. Najpierw pojawił się .NET, a dopiero niedawno odszedł od tego (IMHO, całkiem uzasadnionego i odpowiedniego) kierunku.

Wszystkie takie koncepcje wymagają czasu, aby przemysł je przetworzył.

Logika SK
źródło
Zredagowałem oryginalne pytanie na podstawie odpowiedzi.
Jetti
Funkcje pierwszej klasy! = Zamknięcia (a GC jest znacznie bardziej potrzebne dla tych ostatnich). Chociaż tak, są ze sobą ściśle powiązane i rzadko, jeśli w ogóle, widzisz je osobno.
@delnan, bez przynajmniej jakiejś formy leksykalnych zamknięć, funkcje nie są możliwe do skonstruowania i nie można ich zbudować w czasie wykonywania, co jest obowiązkowe dla definicji funkcji pierwszej klasy.
SK-logic
Nie, mogłyby uniemożliwić odnosząc się do zmiennych załączając zakresów (lub skopiować wartości zmiennej określoną w momencie tworzenia funkcji - nie wiem, czy to się liczy jako zamknięcia). Wynik nie jest szczególnie użyteczny, ale funkcje można nadal konstruować w czasie wykonywania bez zamykania się, a zatem masz (dziwne) funkcje pierwszej klasy.
1
@ SK-logic: C ++ 0x zapewnia zamknięcie bez odśmiecania zwykłą sztuczką -> jeśli zmienna wykracza poza zakres i nadal przywiązujesz się do odwołania, użycie odwołania jest niezdefiniowanym zachowaniem. Więc to możliwe ... po prostu nakłada na programistę honus.
Matthieu M.,
5

Funkcje pierwszej klasy są o wiele mniej interesujące bez zamknięć.

Zamknięcia nie są tak naprawdę możliwe bez pewnego dynamicznego zarządzania zakresem (odśmiecanie). Jedną z rzeczy, która sprawia, że ​​C / C ++ jest tak wydajny, jest fakt, że o wiele łatwiej jest skompilować język, w którym ręcznie zarządzasz pamięcią, tak że w pewnym sensie C / C ++ jest poza równaniem.

I tak, jest kwestia wpływu OOP. Nie masz już oddzielenia metod i właściwości. Metody same w sobie są właściwościami, które akceptują funkcje jako wartości. W tym kontekście, co tak naprawdę oznacza przeciążenie metod, ponieważ w każdej chwili możesz po prostu wymieniać głupie rzeczy. Czy naprawdę możesz na przykład dodać to do Javy, nie wprowadzając istotnej zmiany paradygmatu na cały język? Gdzie jest umowa lub poczucie (fałszywe IMO) bezpieczeństwa, jakie ludzie uzyskują, wiedząc, że konstrukcja gwarantuje, że zawsze będzie działać tak samo za każdym razem? Rozsądne może być traktowanie funkcji powiązanych z obiektami jako metod jako odmiennego organizmu w językach, który pozwalał przede wszystkim na niezależne funkcjonowanie, ale w Javie nie jest tak naprawdę opcją.

W JavaScript, OOP i funkcjonalność są ze sobą ściśle powiązane i osobiście ciężko byłoby mi zobaczyć pierwszorzędne funkcje jako nic innego, jak tylko wbudowane w języki, w których nie były. Wymaga to jednak wielu innych zmian zmieniających paradygmat, w tym wysokiego poziomu zmienności obiektów i samych metod, a także możliwości wywoływania funkcji tak, jakby były członkami dowolnego obiektu w locie.

Języki to nie tylko zestawy narzędzi pełnych hickey do wykonywania zadań w większości. Są paradygmatami. Wiązki pomysłów, strategii i opinii, które są ze sobą powiązane w sposób, który jest połączony (lub wydaje się związany). W wielu przypadkach funkcje rzeczownika i czasownika tak naprawdę po prostu nie pasują do paradygmatu lub w najlepszym przypadku są niedoskonałe.

To powiedziawszy, czuję, że brakuje mi ramienia, gdy zmuszony jestem kodować bez nich.

Erik Reppen
źródło
Możesz traktować wszystkie metody jako zapieczętowane lub po prostu zapieczętować te, na których najbardziej ci zależy, i wszystko będzie dobrze.
Alexei Averchenko
@AlexeiAverchenko Z przyjemnością stwierdzam, że nie jestem w 100% pewien, co masz na myśli. Przepraszam, że do tej pory brakowało mi komentarza. Googling „zamknięte metody”.
Erik Reppen
4

To naprawdę nie jest niemożliwe. Ale to kolejna funkcja, a budżet na złożoność jest ograniczony. Może to zostać uznane przez projektanta języka za niepotrzebne (lub nawet nie przydarzyć się) - „dlaczego, do cholery, musielibyśmy przekazywać funkcje? Ten język służy do programowania systemu operacyjnego!”. Może także wymagać znacznego wysiłku, aby połączyć się z resztą języka. Na przykład typ funkcji może wymagać poważnych dodatków do systemu typów (np. W Javie), a nawet więcej, jeśli masz przeciążenie (dzięki @Renesis za wskazanie tego). Musisz także podać kod biblioteki, aby skorzystać z realnego - nikt nie chce się definiować map.

Może to również utrudnić osiągnięcie innych celów języka:

  • Optymalizacja staje się „trudniejsza” (w wielu przypadkach nie jest trudniejsza, ale wymaga innego podejścia niż te, do których jesteś przyzwyczajony). Na przykład, jeśli funkcje globalne mogą zostać ponownie przypisane, musisz udowodnić, że fnigdy nie jest przypisane ponownie, zanim wstawisz je.
  • Podobnie, jeśli tworzysz obiekty w pełni wyposażone w funkcje, potrzebujesz inteligentnego kompilatora i JIT, aby usunąć związane z tym koszty ogólne i sprawić, że wywoływanie będzie tak wydajne (pod względem szybkości i przestrzeni) jak przekazywanie argumentów i zwracanie adresu oraz przeskakiwanie do jakiś adres.
  • Jak już wspomniano, jest to kolejna pełna funkcja i pierwsze kroki w kierunku wspierania kolejnego paradygmatu - jeśli chcesz prostego języka, być może nie możesz sobie pozwolić na dodanie funkcji pierwszej klasy bez rzucania innymi rzeczami (które możesz uznać za znacznie ważniejsze) na zewnątrz.
  • Być może po prostu nie pasuje dobrze do reszty języka. Jeśli projektujesz język tak, aby był np. Najlepszą inkarnacją OOP od Smalltalk, możesz skupić się na tym, zamiast oferować inny, zupełnie inny paradygmat. Zwłaszcza jeśli trudno jest zintegrować oba (patrz wyżej). Dobrze wykonanych paradygmatów N przyjemniej jest programować niż źle wykonanych paradygmatów N + 1.

W tym samym duchu można zapytać, dlaczego jakikolwiek język programowania L1 nie ma funkcji F w zupełnie innym języku L2.


źródło
1

Myślę, że pierwszym problemem jest nieporozumienie, że obiektowo-funkcjonalne nie można mieszać. Szybka lista języków opisanych, przynajmniej przez Wikipedię, jako: JavaScript , ActionScript , Python , C # , F # .

Drugi problem wydaje się być definicją funkcji pierwszej klasy - dlaczego na przykład nie sądzisz, że C # je ma?

Nie jestem ekspertem od języków funkcjonalnych, więc jeśli się mylę, popraw mnie w komentarzach, ale rozumiem, że samo włączenie funkcji pierwszej klasy mniej więcej określa, czy język obsługuje paradygmat funkcjonalny .

Jakie jest prawdziwe pytanie?

Rozumiejąc, że języki zorientowane obiektowo mogą być również funkcjonalne, a te języki zawierają funkcje najwyższej klasy, brzmi to tak, jakbyś szukał pytania, dlaczego niektóre języki zorientowane obiektowo nie zawierają funkcji najwyższej klasy?

Przeciążenie funkcji

Jednym z głównych powodów tego jest trudność w zdefiniowaniu funkcji pierwszej klasy, gdy język wybrał również obsługę przeciążenia funkcji (przykład w Javie):

public int dispatchEvent(Event event);
public int dispatchEvent(String event, Object data);

Do jakiej dispatchEventfunkcji odnosi się zmienna?

Programowanie klasowe

Programowanie oparte na klasach nie uniemożliwia językom obsługi obiektów pierwszej klasy, ale może sprawić, że będzie bardziej mylące lub dodawać pytania, na które należy odpowiedzieć.

Na przykład ActionScript, jako język ECMAScript, zaczynał od znacznie bardziej funkcjonalnego paradygmatu, takiego jak JavaScript. Funkcje nie były z natury związane z obiektami, można je w zasadzie wywoływać z dowolnym obiektem, ponieważ ma on zasięg w momencie wykonywania.

Kiedy dodajesz (niedynamiczne) klasy, masz funkcje, które są z natury związane z instancją, a nie tylko samą klasą. To powoduje kilka zmarszczek w specyfikacji, Function.applyktórej tak naprawdę nie wykopałem, aby dowiedzieć się, jak sobie z tym poradzili.

Nicole
źródło
3
„Samo włączenie funkcji pierwszej klasy mniej więcej sprawia, że ​​język jest funkcjonalny”. - Źle. Tak, to warunek konieczny. Ale jest o wiele więcej, jak (żeby wymienić tylko trzy) unikanie stanu zmiennego, skupienie się na wyrażeniach i zastosowaniu funkcji zamiast instrukcji sekwencyjnych i nacisk na przejrzystość referencyjną.
@delnan W takim razie należy poprawić Wikipedię ... Chyba że „język funkcjonalny” i „język wspierający paradygmat funkcjonalny” to dwie różne rzeczy.
Nicole
1
@Rensis: To są różne rzeczy. Możesz stworzyć coś takiego jak obiekty (nawet włączając vtables) w C, ale to nie jest ładne. Przejście od „ma pierwszorzędne funkcje” do „jest funkcjonalnym językiem” nie jest wcale takie złe, ale nadal jest różnicą. Ponadto wiki (poprawnie) stwierdza na stronie, do której prowadzi link „Te funkcje są niezbędne dla funkcjonalnego stylu programowania [...]”. (tj .: nie „cecha definiująca”) i wymienia kilka innych funkcji na stronie FP.
@ delnan, w porządku, poprawiłem swoje brzmienie. Miałem tylko na myśli warunek, aby język był uznawany za obsługujący paradygmat funkcjonalny - podobnie jak strona Wikipedii dla każdego z wymienionych przeze mnie języków. Nie chciałem powiedzieć, że to wszystko jest częścią funkcjonalnego stylu programowania .
Nicole
0

Zastanawiałem się nad tym samym. Kiedy jestem już w języku z lambdas, używam ich dużo. Nawet PHP staje się lepsze, gdy zdajesz sobie sprawę, że możesz zrobić zamknięcie z php 5.3 (nie jest tak przyjemne jak seplenienie, ale wciąż lepsze)

Zachary K.
źródło