Jak zwolennicy programowania funkcjonalnego odpowiedzieliby na to stwierdzenie w Code Complete?

30

Na stronie 839 drugiego wydania Steve McConnell omawia wszystkie sposoby, w jakie programiści mogą „pokonać złożoność” w dużych programach. Jego wskazówki kończą się tym stwierdzeniem:

„Programowanie obiektowe zapewnia poziom abstrakcji, który dotyczy jednocześnie algorytmów i danych , rodzaj abstrakcji, której nie zapewnił sam rozkład funkcjonalny”.

W połączeniu z konkluzją, że „zmniejszenie złożoności jest prawdopodobnie najważniejszym kluczem do bycia efektywnym programistą” (ta sama strona), wydaje się, że jest to wyzwanie dla programowania funkcjonalnego.

Debata między FP i OO jest często omawiana przez zwolenników FP wokół kwestii złożoności, które wynikają konkretnie z wyzwań związanych z współbieżnością lub równoległością. Ale współbieżność z pewnością nie jest jedynym rodzajem złożoności, który programiści muszą pokonać. Być może skupienie się na zmniejszeniu jednego rodzaju złożoności znacznie go zwiększa w innych wymiarach, tak że w wielu przypadkach zysk nie jest wart kosztu.

Jeśli zmienilibyśmy warunki porównania FP i OO z konkretnych kwestii, takich jak współbieżność lub możliwość ponownego użycia, do zarządzania globalną złożonością, jak wyglądałaby ta debata?

EDYTOWAĆ

Kontrast, który chciałem podkreślić, polega na tym, że OO wydaje się hermetyzować i abstrahować od złożoności zarówno danych, jak i algorytmów, podczas gdy programowanie funkcjonalne wydaje się zachęcać do pozostawiania szczegółów implementacji struktur danych bardziej „ujawnionych” w całym programie.

Patrz np Stuart Halloway (zwolennikiem Clojure FP) tutaj mówiąc, że „nadmierne specyfikacja typów danych” jest „negatywna konsekwencja idiomatycznym stylu oo” i faworyzowanie konceptualizacji adresową jako prosty wektora lub map zamiast bogatszej obiektu OO z dodatkowymi (nie wektorowymi i niemapowymi) właściwościami i metodami. (Również zwolennicy projektowania i zarządzania opartego na domenie mogą powiedzieć, że wystawienie książki adresowej jako wektora lub mapy prześwietla enkapsulowane dane metodami, które są nieistotne, a nawet niebezpieczne z punktu widzenia domeny).

dan
źródło
3
+1 pomimo tego, że pytanie zostało sformułowane raczej antagonistycznie, to dobre pytanie.
mattnz 12.01.12
16
Jak wielu stwierdziło w odpowiedziach, rozkład funkcjonalny i programowanie funkcjonalne to dwie różne bestie. Stąd wniosek, że „wydaje się to raczej wyzwaniem dla programowania funkcjonalnego” jest po prostu błędny, nie ma z tym nic wspólnego.
Fabio Fracassi
5
Najwyraźniej wiedza McConnela na temat nowoczesnych funkcjonalnych systemów danych i wysokiej klasy modułów pierwszej klasy jest nieco niejednolita. Jego wypowiedź jest całkowicie nonsensowna, ponieważ mamy moduły pierwszej klasy i funktory (patrz SML), klasy typów (patrz Haskell). To tylko kolejny przykład tego, że sposób myślenia OO jest bardziej religią niż szacunkową metodologią projektowania. A tak przy okazji, skąd ta rzecz na temat współbieżności? Większość programistów funkcjonalnych wcale nie dba o równoległość.
SK-logic
6
@ SK-logic Wszystko, co McConnell powiedział, to to, że „sam funkcjonalny rozkład” nie zapewnia takich samych sposobów abstrakcji jak OOP, co wydaje mi się dość bezpiecznym stwierdzeniem. Nigdzie nie mówi, że języki FP nie mają tak silnych abstrakcji jak OOP. W rzeczywistości wcale nie wspomina o językach FP. To tylko interpretacja OP.
sepp2k
2
@ sepp2k, ok, rozumiem. Jednak nadal można zbudować bardzo złożony i wielowarstwowy system struktur danych i abstrakcji przetwarzania na podstawie funkcjonalnego rozkładu prawie czystego rachunku lambda - poprzez symulację zachowania modułów. W ogóle nie potrzeba abstrakcji OO.
SK-logic

Odpowiedzi:

13

Pamiętaj, że książka została napisana ponad 20 lat temu. Dla ówczesnych profesjonalnych programistów FP nie istniała - była całkowicie w sferze naukowców i badaczy.

Musimy wykadrować „rozkład funkcjonalny” we właściwym kontekście pracy. Autor nie odnosi się do programowania funkcjonalnego. Musimy powiązać to z „programowaniem strukturalnym” i GOTOnapełnionym bałaganem, który się przed nim pojawił. Jeśli twoim punktem odniesienia jest stary FORTRAN / COBOL / BASIC, który nie miał funkcji (być może, gdybyś miał szczęście, uzyskałby jeden poziom GOSUB), a wszystkie twoje zmienne są globalne, jest w stanie rozbić twój program na warstwy funkcji jest dużym dobrodziejstwem.

OOP to dalsze udoskonalenie tego rodzaju „rozkładu funkcjonalnego”. Nie tylko możesz łączyć instrukcje w funkcje, ale także grupować powiązane funkcje z danymi, nad którymi pracują. Rezultatem jest jasno zdefiniowany fragment kodu, który można przejrzeć i zrozumieć (najlepiej) bez konieczności gonienia całej bazy kodu, aby dowiedzieć się, co jeszcze może działać na twoich danych.

Sean McSomething
źródło
27

Wyobrażam sobie, że zwolennicy programowania funkcjonalnego twierdzą, że większość języków FP zapewnia więcej sposobów abstrakcji niż „sam funkcjonalny rozkład” i faktycznie dopuszcza sposoby abstrakcji porównywalne pod względem mocy do języków zorientowanych obiektowo. Na przykład można przytoczyć klasy typów Haskella lub moduły wyższego rzędu ML jako takie metody abstrakcji. Zatem stwierdzenie (które jestem prawie pewien, że dotyczy orientacji obiektowej vs. programowanie proceduralne, a nie programowanie funkcjonalne) nie ma do nich zastosowania.

Należy również zauważyć, że FP i OOP są koncepcjami ortogonalnymi i nie wykluczają się wzajemnie. Dlatego nie ma sensu porównywać ich ze sobą. Można bardzo dobrze porównać „imperatywny OOP” (np. Java) vs. „funkcjonalny OOP” (np. Scala), ale cytowane przez ciebie stwierdzenie nie będzie miało zastosowania do tego porównania.

sepp2k
źródło
10
+1 „Rozkład funkcjonalny”! = „Programowanie funkcjonalne”. Pierwszy opiera się na klasycznym kodowaniu sekwencyjnym z wykorzystaniem waniliowych struktur danych bez dziedziczenia, enkapsulacji i polimorfizmu (lub tylko ręcznie walcowane). Drugi wyraża rozwiązania za pomocą rachunku lambda. Dwie zupełnie różne rzeczy.
Binary Worrier
4
Przepraszamy, ale fraza „programowanie proceduralne” uparcie odmawiała wcześniejszego przypomnienia. „Rozkład funkcjonalny” jest dla mnie o wiele bardziej wskazujący na programowanie proceduralne niż programowanie funkcjonalne.
Binary Worrier
Tak, masz rację. Zakładałem, że Programowanie Funkcjonalne faworyzuje funkcje wielokrotnego użytku działające na tych samych prostych strukturach danych (listy, drzewa, mapy) w kółko i faktycznie twierdzi, że jest to zaletą w stosunku do OO. Zobacz Stuart Halloway (zwolennik Clojure FP), który mówi, że „nadmierna specyfikacja typów danych” jest „negatywną konsekwencją idiomatycznego stylu OO” i sprzyja konceptualizacji książki adresowej jako wektora lub mapy zamiast bogatszego obiektu OO z innymi (nie -wektorowe i niepodobne do map) właściwości i metody.
dan
Link do cytatu Stuart Halloway: thinkrelevance.com/blog/2009/08/12/...
Dan
2
@dan Może tak się dzieje w dynamicznie pisanym języku Clojure (nie wiem, nie używam Clojure), ale myślę, że wyciągnięcie z tego wniosku jest niebezpieczne, że tak właśnie jest w FP. Na przykład ludzie Haskell wydają się być bardzo wielcy w zakresie abstrakcyjnych typów i ukrywania informacji (być może nie tak bardzo jak ludzie Java).
sepp2k
7

Uważam, że programowanie funkcjonalne jest niezwykle pomocne w zarządzaniu złożonością. Zwykle myślisz o złożoności w inny sposób, definiując ją jako funkcje, które działają na niezmiennych danych na różnych poziomach, zamiast enkapsulacji w sensie OOP.

Na przykład niedawno napisałem grę w Clojure, a cały stan gry został zdefiniowany w jednej niezmiennej strukturze danych:

(def starting-game-state {:map ....
                          :player ....
                          :weather ....
                          :other-stuff ....}

Główną pętlę gry można zdefiniować jako stosowanie w pętli niektórych czystych funkcji do stanu gry:

 (loop [initial-state starting-game-state]
   (let [user-input (get-user-input)
         game-state (update-game initial-state user-input)]
     (draw-screen game-state)
     (if-not (game-ended? game-state) (recur game-state))))

Wywoływana jest kluczowa funkcja update-game, która uruchamia krok symulacji, biorąc pod uwagę poprzedni stan gry i dane wprowadzone przez użytkownika, i zwraca nowy stan gry.

Więc gdzie jest złożoność? Moim zdaniem udało się to całkiem dobrze:

  • Z pewnością funkcja aktualizacji gry wykonuje wiele pracy, ale sama jest zbudowana przez skomponowanie innych funkcji, więc sama w sobie jest całkiem prosta. Po przejściu o kilka poziomów funkcje są nadal dość proste, np. „Dodaj obiekt do kafelka mapy”.
  • Z pewnością stan gry to struktura dużych zbiorów danych. Ale znowu, to jest po prostu zbudowane przez skomponowanie struktur danych niższego poziomu. Ponadto są to „czyste dane” zamiast mieć wbudowane jakiekolwiek metody lub wymaganą definicję klasy (jeśli chcesz, możesz to potraktować jako bardzo niezmienny niezmienny obiekt JSON), więc jest bardzo mało podstaw.

OOP może również zarządzać złożonością poprzez enkapsulację, ale jeśli porównasz to do OOP, funkcjonalność ma bardzo duże zalety:

  • Struktura danych stanu gry jest niezmienna, więc wiele procesów można łatwo wykonać równolegle. Na przykład całkowicie bezpieczne jest wyświetlanie ekranu wywołującego renderowanie w innym wątku niż logika gry - nie mogą one wpływać na siebie nawzajem ani widzieć niespójnego stanu. Jest to zaskakująco trudne w przypadku dużego wykresu obiektów zmiennych ......
  • W każdej chwili możesz zrobić migawkę stanu gry. Powtórki są trywialne (dzięki trwałym strukturom danych Clojure kopie nie zajmują prawie żadnej pamięci, ponieważ większość danych jest współdzielona). Możesz także uruchomić grę aktualizacyjną, aby „przewidzieć przyszłość”, aby pomóc AI na przykład ocenić różne ruchy.
  • Nigdzie nie musiałem dokonywać żadnych trudnych kompromisów, aby dopasować się do paradygmatu OOP, takich jak zdefiniowanie sztywnej dziedziczności klas. W tym sensie funkcjonalna struktura danych zachowuje się bardziej jak elastyczny system oparty na prototypach.

Na koniec, dla osób, które są zainteresowane większą wiedzą na temat zarządzania złożonością w językach funkcjonalnych i OOP, zdecydowanie polecam film z przemówienia Richa Hickeya Simple Made Easy (nakręcony podczas konferencji technologicznej Strange Loop )

mikera
źródło
2
Sądzę, że gra jest jednym z najgorszych możliwych przykładów wykazujących rzekome „korzyści” wymuszonej niezmienności. W grze ciągle coś się zmienia, co oznacza, że ​​musisz cały czas odbudowywać swój stan gry. A jeśli wszystko jest niezmienne, oznacza to, że musisz nie tylko odbudować stan gry, ale wszystko na wykresie, które zawiera odniesienie do niego lub to, które zawiera odniesienie do tego, i tak dalej, aż do recyklingu całego program przy 30+ FPS, z mnóstwem GC churn do uruchomienia! Nie ma sposobu, aby uzyskać z tego dobrą wydajność ...
Mason Wheeler,
7
Oczywiście gry są trudne z niezmiennością - dlatego wybrałem to, aby zademonstrować, że nadal może działać! Byłbyś jednak zaskoczony, co mogą zrobić trwałe struktury danych - większość stanu gry nie musi być odbudowywana, tylko rzeczy, które się zmieniają. I na pewno jest trochę kosztów ogólnych, ale to tylko niewielki stały czynnik. Daj mi wystarczającą liczbę rdzeni, a pokonam twój jednowątkowy silnik gry C ++ .....
mikera
3
@Mason Wheeler: Właściwie, to jest możliwe, aby uzyskać praktycznie równe (tak dobre jak z mutacji) Wydajność z niezmiennych obiektów bez większych GC w ogóle. Sztuką w Clojure jest używanie trwałych struktur danych : są one niezmienne dla programisty, ale w rzeczywistości można je modyfikować pod maską. Najlepsze z obu światów.
Joonas Pulakka
4
@quant_dev Więcej rdzeni jest tańszych niż lepsze rdzenie ... escapistmagazine.com/news/view/…
deworde
6
@ quant_dev - to nie jest wymówka, to matematyczny i architektoniczny fakt, że lepiej jest ponosić stałe koszty ogólne, jeśli możesz to nadrobić, skalując swoją wydajność prawie liniowo z liczbą rdzeni. Języki funkcjonalne będą ostatecznie oferować doskonałą wydajność, ponieważ doszliśmy do końca linii w zakresie wydajności pojedynczego rdzenia, a wszystko będzie dotyczyło współbieżności i równoległości w przyszłości. podejścia funkcjonalne (a zwłaszcza niezmienność) są ważne w wykonaniu tej pracy.
mikera
3

„Programowanie obiektowe zapewnia poziom abstrakcji, który stosuje się jednocześnie do algorytmów i danych, rodzaj abstrakcji, której nie zapewnił sam rozkład funkcjonalny”.

Sam rozkład funkcjonalny nie wystarczy do stworzenia jakiegokolwiek algorytmu lub programu: musisz również przedstawić dane. Myślę, że powyższe stwierdzenie domyślnie zakłada (a przynajmniej można to zrozumieć), że „dane” w przypadku funkcjonalnym są najbardziej szczątkowe: po prostu listy symboli i nic więcej. Programowanie w takim języku jest oczywiście niezbyt wygodne. Jednak wiele, zwłaszcza nowe i nowoczesne, funkcjonalne (lub wieloparadigmowe) języki, takie jak Clojure, oferują bogate struktury danych: nie tylko listy, ale także ciągi znaków, wektory, mapy i zbiory, rekordy, struktury - i obiekty! - z metadanymi i polimorfizmem.

Trudno kwestionować ogromny praktyczny sukces abstrakcji OO. Ale czy to ostatnie słowo? Jak pisałeś, problemy z współbieżnością już są głównym problemem, a klasyczny OO nie zawiera żadnego pojęcia o współbieżności. W rezultacie de facto rozwiązania OO do radzenia sobie z współbieżnością to po prostu nakładająca się taśma izolacyjna: działa, ale łatwo ją zepsuć, zabiera znaczną ilość zasobów mózgu od niezbędnego zadania i nie skaluje się dobrze. Może można wziąć to, co najlepsze z wielu światów. Do tego dążą współczesne języki wieloparadygmatu.

Joonas Pulakka
źródło
1
Gdzieś słyszałem zdanie „OO in large, FP in small” - myślę, że Michael Feathers to zacytował. Oznacza to, że FP może być dobry dla niektórych części dużego programu, ale ogólnie powinien być OO.
dan
Ponadto zamiast używać Clojure do wszystkiego, nawet rzeczy, które są wyrażane w bardziej tradycyjny sposób w bardziej tradycyjnej składni OO, możesz użyć Clojure do bitów przetwarzania danych tam, gdzie jest on czystszy, i używać Java lub innego języka OO dla innych bitów. Programowanie Polyglot zamiast programowania wieloparadigmowego z tym samym językiem dla wszystkich części programu. (W pewnym sensie jak większość aplikacji internetowych używa SQL i OO dla różnych warstw.)?
dan
@dan: Użyj narzędzia, które najlepiej pasuje do pracy. W programowaniu poliglotycznym kluczowym czynnikiem jest wygodna komunikacja między językami, a Clojure i Java nie mogłyby lepiej grać razem . Wierzę, że większość znaczących programów Clojure używa tu i tam przynajmniej części standardowych bibliotek JDK.
Joonas Pulakka
2

Zmienny stan jest źródłem większości zawiłości i problemów związanych z programowaniem i projektowaniem oprogramowania / systemu.

OO obejmuje stan zmienny. FP nie znosi stanu zmiennego.

Zarówno OO, jak i FP mają swoje zastosowania i słodkie punkty. Wybierz mądrze. I pamiętaj przysłowie: „Zamknięcia są obiektami biednego człowieka. Obiekty są zamknięciem biednego człowieka”.

Maglob
źródło
3
Nie jestem pewien, czy twoje wstępne twierdzenie jest prawdziwe. Źródło „większości” zawiłości? W programowaniu, które wykonałem lub widziałem, problemem jest nie tyle zmienny stan, co brak abstrakcji i nadmiar szczegółów w kodzie.
dan
1
@Dan: Interesujące. Widziałem wręcz przeciwnie: problemy wynikają z nadużywania abstrakcji, co utrudnia zarówno zrozumienie, jak i, w razie potrzeby, naprawienie szczegółów tego, co się właściwie dzieje.
Mason Wheeler,
1

Programowanie funkcjonalne może mieć obiekty, ale obiekty te są zwykle niezmienne. Czyste funkcje (funkcje bez skutków ubocznych) działają następnie na tych strukturach danych. Możliwe jest tworzenie niezmiennych obiektów w obiektowych językach programowania, ale nie zostały one zaprojektowane do tego i nie tak się je stosuje. Utrudnia to rozumowanie programów zorientowanych obiektowo.

Weźmy bardzo prosty przykład. Powiedzmy, że Oracle zdecydowało, że Java Strings powinna mieć metodę odwrotną i napisałeś następujący kod.

String x = "abc";
StringBuffer y = new StringBuffer(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString());

co ocenia ostatnia linia? Potrzebujesz specjalnej wiedzy na temat klasy String, aby wiedzieć, że będzie to fałsz.

Co jeśli stworzę własną klasę WuHoString

String x = "abc";
WuHoString y = new WuHoString(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString())

Nie można wiedzieć, co ocenia ostatnia linia.

W stylu programowania funkcjonalnego byłby zapisany w następujący sposób:

String x;
equals(toString(reverse(x)), toString(reverse(WuHoString(x))))

i powinno być prawdą.

Jeśli tak trudno jest zrozumieć 1 funkcję w jednej z najbardziej podstawowych klas, to zastanawia się, czy wprowadzenie tej koncepcji obiektów zmiennych zwiększyło lub zmniejszyło złożoność.

Oczywiście istnieją różnego rodzaju definicje tego, co stanowi obiektowe i co oznacza być funkcjonalnym i co to znaczy mieć jedno i drugie. Dla mnie możesz mieć „funkcjonalny styl programowania” w languagess, który nie ma rzeczy takich jak funkcje pierwszej klasy, ale stworzono dla niego inne języki.

WuHoUnited
źródło
3
To trochę zabawne, że mówisz, że języki OO nie są budowane dla niezmiennych obiektów, a następnie użyj przykładu z ciągami znaków (które są niezmienne w większości języków OO, w tym Java). Powinienem również zauważyć, że istnieją języki OO (a raczej wielo-paradygmat), które zostały zaprojektowane z naciskiem na niezmienne obiekty (na przykład Scala).
sepp2k
@ sepp2k: Przyzwyczaj się do tego. Zwolennicy FP zawsze rzucają dziwne, wymyślone przykłady, które nie mają nic wspólnego z kodowaniem w świecie rzeczywistym. Jest to jedyny sposób, aby sprawić, by podstawowe koncepcje FP, takie jak wymuszona niezmienność, wyglądały dobrze.
Mason Wheeler,
1
@Mason: Hę? Czy najlepszym sposobem na poprawienie niezmienności byłoby powiedzenie „Java (i C #, python itp.) Używa niezmiennych ciągów i działa świetnie”?
sepp2k
1
@ sepp2k: Jeśli ciągi niezmienne działają tak wspaniale, dlaczego wciąż pojawiają się klasy stylu StringBuilder / StringBuffer? To tylko kolejny przykład inwersji abstrakcji, która staje ci na drodze.
Mason Wheeler,
2
Wiele języków obiektowych pozwala tworzyć niezmienne obiekty. Ale koncepcja powiązania metod z klasą naprawdę zniechęca to z mojego punktu widzenia. Przykład ciągu nie jest tak naprawdę wymyślonym przykładem. Za każdym razem, gdy wywołuję mehtod w java, ryzykuję, czy moje parametry zostaną zmutowane w ramach tej funkcji.
WuHoUnited
0

Myślę, że w większości przypadków klasyczna abstrakcja OOP nie obejmuje złożoności współbieżności. Dlatego OOP (w swoim pierwotnym znaczeniu) nie wyklucza FP i dlatego widzimy rzeczy takie jak scala.

duyt
źródło
0

Odpowiedź zależy od języka. Na przykład Lisps ma naprawdę porządnie, że kod to dane - algorytmy, które piszesz, to tak naprawdę tylko listy Lisp! Przechowujesz dane w taki sam sposób jak piszesz program. Ta abstrakcja jest jednocześnie prostsza i dokładniejsza niż OOP i pozwala ci robić naprawdę fajne rzeczy (sprawdź makra).

Haskell (i podobny język, jak sądzę) ma zupełnie inną odpowiedź: algebraiczne typy danych. Algebraiczny typ danych jest podobny do Cstruktury, ale ma więcej opcji. Te typy danych zapewniają abstrakcję potrzebną do modelowania danych; funkcje zapewniają abstrakcję potrzebną do modelowania algorytmów. Klasy typów i inne zaawansowane funkcje zapewniają jeszcze wyższy poziom abstrakcji w obu przypadkach.

Na przykład, dla zabawy pracuję nad językiem programowania o nazwie TPL. Algebraiczne typy danych naprawdę ułatwiają reprezentowanie wartości:

data TPLValue = Null
              | Number Integer
              | String String
              | List [TPLValue]
              | Function [TPLValue] TPLValue
              -- There's more in the real code...

To, co mówi - w bardzo wizualny sposób - jest to, że TPLValue (dowolna wartość w moim języku) może być a Nulllub a Numberz Integerwartością lub nawet Functionz listą wartości (parametry) i wartością końcową (treść ).

Następnie mogę użyć klas typów, aby zakodować niektóre typowe zachowania. Na przykład, mógłbym zrobić TPLValuei którego wystąpienie Showoznacza, że ​​można go przekonwertować na ciąg.

Ponadto mogę używać własnych klas typów, gdy muszę określić zachowanie niektórych typów (w tym tych, których sam nie zaimplementowałem). Na przykład mam Extractableklasę typów, która pozwala mi napisać funkcję, która przyjmuje a TPLValuei zwraca odpowiednią wartość normalną. W ten sposób extractmożna przekonwertować a Numberna a Integerlub Stringa Stringtak długo, jak Integeri Stringsą instancjami Extractable.

Wreszcie główna logika mojego programu obejmuje kilka funkcji, takich jak evali apply. To naprawdę rdzeń - biorą TPLValues i zamieniają je w więcej TPLValues, a także obsługę stanu i błędów.

Ogólnie rzecz biorąc, abstrakcje używam w moim kodu Haskell są rzeczywiście bardziej wydajne niż to, co użyłem w języku OOP.

Tikhon Jelvis
źródło
Tak, muszę kochać eval. „Hej, spójrz na mnie! Nie muszę pisać własnych dziur w zabezpieczeniach; mam usterkę wykonania dowolnego kodu wbudowaną w język programowania!” Łączenie danych z kodem jest główną przyczyną jednej z dwóch najpopularniejszych klas luk w zabezpieczeniach wszechczasów. Za każdym razem, gdy zobaczysz, że ktoś został zhakowany z powodu ataku iniekcji SQL (między innymi), dzieje się tak dlatego, że jakiś programista nie wie, jak właściwie oddzielić dane od kodu.
Mason Wheeler,
evalnie zależy w dużej mierze od struktury Lispa - możesz mieć evaltakie języki jak JavaScript i Python. Prawdziwa moc pochodzi z pisania makr, które są w zasadzie programami, które działają na programy takie jak dane i generują inne programy. To sprawia, że ​​język jest bardzo elastyczny i łatwe tworzenie potężnych abstrakcji.
Tikhon Jelvis
3
Tak, słyszałem już wiele razy o „makrach są niesamowite”. Ale nigdy nie widziałem rzeczywistego przykładu makra Lisp, który robi coś, co 1) jest praktyczne, a czegoś, co naprawdę chciałbyś zrobić w kodzie rzeczywistym i 2) nie można osiągnąć tak łatwo w żadnym języku obsługującym funkcje.
Mason Wheeler,
1
@MasonWheeler zwarcie and. zwarcie or. let. let-rec. cond. defn. Żadnego z nich nie można zaimplementować za pomocą funkcji w aplikacyjnych językach porządkowych. for(lista ze zrozumieniem). dotimes. doto.
1
@MattFenwick: OK, naprawdę powinienem dodać trzeci punkt do moich dwóch powyższych: 3) nie jest już wbudowany w żaden rozsądny język programowania. Ponieważ to jedyne naprawdę przydatne przykłady makr, jakie kiedykolwiek widziałem, a kiedy mówisz „Hej, spójrz na mnie, mój język jest tak elastyczny, że mogę zaimplementować własne zwarcie and!” Słyszę: „hej, spójrz na mnie, mój język jest tak okaleczony, że nawet nie ma zwarcia andi muszę wszystko wymyślić na nowo !”
Mason Wheeler
0

Cytowane zdanie, jak widzę, nie ma już żadnej ważności.

Współczesne języki OO nie mogą abstrakcji nad typami, których rodzaj nie jest *, tzn. Typy o wyższym rodzaju są nieznane. Ich system typów nie pozwala wyrazić idei „jakiegoś kontenera z elementami Int, który pozwala zamapować funkcję na elementach”.

Stąd ta podstawowa funkcja jak Haskells

fmap :: Functor f => (a -> b) -> f a -> f b 

nie można łatwo napisać w Javie *), na przykład przynajmniej nie w sposób bezpieczny dla typu. Dlatego, aby uzyskać podstawową funkcjonalność, musisz napisać wiele bojlerów, ponieważ potrzebujesz

  1. metoda zastosowania prostej funkcji do elementów listy
  2. metoda zastosowania tej samej prostej funkcji do elementów tablicy
  3. metoda zastosowania tej samej prostej funkcji do wartości skrótu,
  4. .... zestaw
  5. .... drzewo
  6. ... 10. testy jednostkowe tego samego

A jednak te pięć metod to w zasadzie ten sam kod, daj lub weź trochę. Natomiast w Haskell potrzebowałbym:

  1. Instancja Functor dla listy, tablicy, mapy, zestawu i drzewa (głównie predefiniowana lub może zostać automatycznie wygenerowana przez kompilator)
  2. prosta funkcja

Zauważ, że nie zmieni się to w Javie 8 (wystarczy, że można łatwiej zastosować funkcje, ale wtedy dokładnie pojawi się powyższy problem. Dopóki nie masz funkcji wyższego rzędu, najprawdopodobniej nawet nie w stanie zrozumieć, do czego nadają się typy wyższego rzędu).

Nawet nowe języki OO, takie jak Cejlon, nie mają wyższych rodzajów. (Ostatnio spytałem Gavina Kinga, a on powiedział mi, że w tej chwili nie było to ważne). Jednak nie wiem o Kotlinie.

*) Aby być uczciwym, możesz mieć interfejs Functor z metodą fmap. Złe jest to, że nie można powiedzieć: Hej, wiem, jak zaimplementować fmap dla klasy biblioteki SuperConcurrentBlockedDoublyLinkedDequeHasMap, drogi kompilatorze, proszę zaakceptować, że od teraz wszystkie SuperConcurrentBlockedDoublyLinkedDequeHasMaps są Functors.

Ingo
źródło
FTR: the typechecker Cejlon i JavaScript backend teraz zrobić wsparcia wyższy ręką typów (a także wyższe rangi rodzajów). Jest uważany za funkcję „eksperymentalną”. Jednak nasza społeczność ma trudności ze znalezieniem praktycznych zastosowań tej funkcji, więc pozostaje otwarte pytanie, czy będzie to kiedykolwiek „oficjalna” część języka. I nie spodziewaj się, że mają być wspierane przez Java backend na pewnym etapie.
Gavin King,
-2

Każdy, kto kiedykolwiek programował w dBase, wiedziałby, jak przydatne są makr jednoliniowe w tworzeniu kodu wielokrotnego użytku. Chociaż nie programowałem w Lisp, czytałem od wielu innych, którzy przeklinają makra czasu kompilacji. Pomysł wstrzykiwania kodu do kodu w czasie kompilacji jest stosowany w prostej formie w każdym programie C z dyrektywą „włącz”. Ponieważ Lisp może to zrobić za pomocą programu Lisp i ponieważ Lisp jest wysoce refleksyjny, zyskujesz znacznie bardziej elastyczną obsługę.

Każdy programista, który po prostu pobierze dowolny tekst z sieci i przekaże go do swojej bazy danych, nie jest programistą. Podobnie każdy, kto pozwoliłby, aby dane „użytkownika” automatycznie stały się kodem wykonywalnym, jest oczywiście głupi. Nie oznacza to, że zezwalanie programom na manipulowanie danymi w czasie wykonywania, a następnie wykonywanie ich, ponieważ kod jest złym pomysłem. Wierzę, że ta technika będzie niezbędna w przyszłości, w której „inteligentny” kod faktycznie pisze większość programów. Cały „problem danych / kodu” lub nie jest kwestią bezpieczeństwa w języku.

Jednym z problemów związanych z większością języków jest to, że zostały stworzone dla jednej osoby do samodzielnego wykonywania niektórych funkcji. Rzeczywiste programy wymagają, aby wiele osób miało dostęp przez cały czas i jednocześnie z wielu rdzeni i wielu klastrów komputerowych. Bezpieczeństwo powinno być częścią języka, a nie systemu operacyjnego, aw niedalekiej przyszłości będzie.

David Clark
źródło
2
Witamy w Programistach. Zastanów się nad złagodzeniem retoryki w swojej odpowiedzi i poprzyj niektóre swoje roszczenia referencjami zewnętrznymi.
1
Każdy programista, który pozwoliłby, aby dane użytkownika automatycznie stały się kodem wykonywalnym, jest oczywiście ignorantem . Nie głupi. Robienie tego w ten sposób jest często łatwe i oczywiste, a jeśli nie wiedzą, dlaczego jest to zły pomysł i że istnieje lepsze rozwiązanie, nie można ich za to winić. (Każdy, kto wciąż to robi po tym, jak nauczono ich, że jest lepszy sposób, jest oczywiście głupi.)
Mason Wheeler