Po kilku miesiącach nauki i gry z Lispem, zarówno CL, jak i odrobiną Clojure, nadal nie widzę przekonującego powodu, aby napisać coś w nim zamiast C #.
Naprawdę chciałbym mieć jakieś ważne powody lub ktoś, kto zauważyłby, że brakuje mi czegoś naprawdę dużego .
Mocne strony Lisp (według moich badań):
- Zwarta, ekspresyjna notacja - Bardziej niż C #, tak ... ale wydaje mi się, że potrafię wyrazić te pomysły również w C #.
- Domniemana obsługa programowania funkcjonalnego - C # z metodami rozszerzenia LINQ:
- mapcar = .Wybierz (lambda)
- mapcan = .Wybierz (lambda) .Aggregate ((a, b) => a.Union (b))
- samochód / pierwszy = .pierwszy ()
- cdr / rest = .Skip (1) .... itd.
- Obsługa funkcji lambda i wyższego rzędu - C # ma to, a składnia jest prawdopodobnie prostsza:
- „(lambda (x) (body))” versus „x => (body)”
- „# (” z „%”, „% 1”, „% 2” jest miłe w Clojure
- Wysłanie metody oddzielone od obiektów - C # ma to poprzez metody rozszerzenia
- Wysyłanie multimetodowe - C # nie ma tego natywnie, ale mógłbym zaimplementować go jako wywołanie funkcji w ciągu kilku godzin
- Kod to dane (i makra) - być może nie „dostałem” makra, ale nie widziałem żadnego przykładu, w którym pomysł makra nie mógłby zostać zaimplementowany jako funkcja; nie zmienia „języka”, ale nie jestem pewien, czy to jest siła
- DSL - Można to zrobić tylko poprzez kompozycję funkcji ... ale działa
- Nietypowe programowanie „eksploracyjne” - w przypadku struktur / klas, autoproperties i „obiekt” C # działają całkiem dobrze i możesz łatwo przerodzić się w mocniejsze pisanie w miarę postępów
- Działa na sprzęcie innym niż Windows - Tak, więc? Poza uczelnią znam tylko jedną osobę, która nie uruchamia systemu Windows w domu, lub przynajmniej maszynę wirtualną systemu Windows na * nix / Mac. (Z drugiej strony, może to jest ważniejsze niż myślałem i właśnie zostałem poddany praniu mózgu ...)
- REPL za projektowanie od dołu do góry - Ok, przyznaję, że to naprawdę bardzo miłe i tęsknię za tym w C #.
Czego brakuje mi w Lisp (ze względu na połączenie C #, .NET, Visual Studio, Resharper):
- Przestrzenie nazw Nawet przy użyciu metod statycznych lubię wiązać je z „klasą”, aby kategoryzować ich kontekst (wydaje się, że Clojure to ma, a CL nie.)
- Świetna obsługa kompilacji i czasu projektowania
- system typów pozwala mi określić „poprawność” struktur danych, które przekazuję
- wszystko błędnie napisane jest podkreślane w czasie rzeczywistym; Nie muszę czekać, aż środowisko uruchomieniowe się dowie
- ulepszenia kodu (takie jak użycie podejścia FP zamiast imperatywnego) są automatycznie sugerowane
- Narzędzia programistyczne GUI: WinForms i WPF (wiem, że Clojure ma dostęp do bibliotek GUI Java, ale są one dla mnie całkowicie obce).
- Narzędzia do debugowania GUI: punkty przerwania, wejście, przejście, inspektory wartości (tekst, xml, niestandardowe), zegarki, debugowanie według wątku, warunkowe punkty przerwania, okno stosu wywołań z możliwością przeskakiwania do kodu na dowolnym poziomie na stosie
- (Szczerze mówiąc, moje doświadczenie z Emacsem + Szlamem zdawało się to zapewniać, ale jestem częściowo zwolennikiem podejścia opartego na VS GUI)
Bardzo podoba mi się szum wokół Lisp i dałem mu szansę.
Ale czy jest coś, co mogę zrobić w Lisp, czego nie mogę zrobić równie dobrze w C #? Może to być nieco bardziej szczegółowe w C #, ale mam również autouzupełnianie.
czego mi brakuje? Dlaczego powinienem używać Clojure / CL?
AND
lubOR
jako funkcję. Można to zrobić (biorąc pod uwagęLAMBDA
, że jest to również makro), ale nie widzę oczywistego sposobu na zrobienie tego, co nie byłoby do niczego.:
(lub::
nieeksportowanych symboli) do nazywania symboli w pakietach, np. Twój przykład użyłbyhttp:session
ichat:session
.Odpowiedzi:
Makra pozwalają pisać kompilatory bez opuszczania wygody twojego języka. DSL w językach takich jak C # zazwyczaj stanowią tłumacza środowiska wykonawczego. W zależności od domeny może to być problematyczne ze względu na wydajność. Szybkość ma znaczenie , w przeciwnym razie kodowałbyś w interpretowanym języku, a nie w języku C #.
Makra pozwalają również uwzględniać czynniki ludzkie - jaka jest najbardziej przyjazna dla użytkownika składnia dla określonej DSL? Dzięki C # niewiele można zrobić ze składnią.
Więc Lisp pozwala ci uczestniczyć w projektowaniu języka bez poświęcania drogi do wydajności . Chociaż te kwestie mogą nie mieć znaczenia dla Ciebie , są one niezwykle ważne, ponieważ leżą u podstaw wszystkich użytecznych języków programowania zorientowanych na produkcję. W języku C # ten podstawowy element jest w najlepszym wypadku bardzo ograniczony.
Znaczenie makr nie jest tracone w innych językach - przychodzą na myśl szablony Haskell, camlp4. Ale znowu jest to kwestia użyteczności - makra Lisp do tej pory są prawdopodobnie najbardziej przyjazną dla użytkownika, ale potężną implementacją transformacji w czasie kompilacji.
Podsumowując, to, co ludzie zazwyczaj robią w językach takich jak C #, to budowanie DSIL (języków specyficznych dla domeny). Lisp daje ci możliwość zbudowania czegoś znacznie bardziej radykalnego - DSP (Domain Specific Paradigms). Rakieta (wcześniej PLT-Scheme) jest szczególnie inspirująca pod tym względem - mają leniwy język (Lazy Racket), maszynowy (Typed Racket), Prolog i Datalog, wszystkie idiomatycznie osadzone za pośrednictwem makr. Wszystkie te paradygmaty zapewniają potężne rozwiązania dla dużych klas problemów - problemów, których nie najlepiej rozwiązać za pomocą programowania imperatywnego, a nawet paradygmatów FP.
źródło
Prawie wszystko, co pierwotnie było dostępne tylko w Lisps, na szczęście zostało przeniesione na wiele innych współczesnych języków. Największym wyjątkiem jest filozofia „kod to dane” i makra.
Tak, makra są trudne do odczytania. Ogólnie trudno jest mówić o metaprogramowaniu. Jeśli widziałeś jakieś makro, które można zaimplementować jako funkcję, programista robił to źle. Makra należy używać tylko wtedy, gdy funkcja nie działa.
Przykład makra „witaj świecie” polega na napisaniu „jeśli” pod względem warunków. Jeśli naprawdę chcesz poznać moc makr, spróbuj zdefiniować „my-if” w clojure, używając funkcji i obserwuj, jak się psuje. Nie chcę być tajemniczy, po prostu nigdy nie widziałem, żeby ktokolwiek zaczął rozumieć makra na podstawie samego wyjaśnienia, ale ten pokaz slajdów jest całkiem dobrym intrem: http://www.slideshare.net/pcalcado/lisp-macros-in -20 minut z prezentacją clojure
Miałem małe projekty, w których zapisałem dosłownie setki / tysiące linii kodu za pomocą makr (w porównaniu do tego, co zrobiłby w Javie). Znaczna część Clojure jest zaimplementowana w Clojure, co jest możliwe tylko dzięki systemowi makr.
źródło
(defun my-if (test then &optional else) (cond (test (funcall then)) ('otherwise (funcall else))))
Nie jestem ekspertem od Common Lisp, ale znam dość dobrze zarówno C #, jak i Clojure, więc mam nadzieję, że jest to przydatna perspektywa.
W wyniku niewiarygodnie prostej składni filozofia Lisp „code is data” jest znacznie potężniejsza niż cokolwiek, co można zrobić z kompozycją funkcji / szablonami / generycznymi itp. To więcej niż tylko DSL, to generowanie kodu i manipulowanie w czasie kompilacji , jako podstawowa cecha języka. Jeśli spróbujesz uzyskać tego rodzaju funkcje w niehomonicznym języku, takim jak C # lub Java, ostatecznie skończysz na nowo wymyślając Lisp. Stąd słynna dziesiąta reguła Greenspuns: „Każdy wystarczająco skomplikowany program C lub Fortran zawiera ad hoc, nieformalnie określone, wolne od błędów, powolne wdrażanie połowy Common Lisp”. Jeśli kiedykolwiek odkryłeś, że wymyślasz w swojej aplikacji kompletny język szablonów / kontroli przepływu, prawdopodobnie wiesz, co mam na myśli .....
Współbieżność jest unikalną siłą Clojure (nawet w stosunku do innych Lisps), ale jeśli uważasz, że przyszłość programowania będzie oznaczać konieczność pisania aplikacji skalowalnych do wysoce wielordzeniowych maszyn, to naprawdę powinieneś się tym przyjrzeć - jest ładny przełomowy. Clojure STM (Software Transactional Memory) działa, ponieważ Clojure obejmuje niezmienne i bezblokowe konstrukcje współbieżności oparte na nowatorskim oddzieleniu tożsamości i stanu (zobacz doskonałe wideo Richa Hickeya na temat tożsamości i stanu ). Przeszczepienie tego rodzaju możliwości współbieżności na środowisko języka / środowiska wykonawczego byłoby bardzo bolesne, w którym znaczna część interfejsów API działa na obiektach zmiennych.
Programowanie funkcjonalne - Lisps kładzie nacisk na programowanie z funkcjami wyższego rzędu. Masz rację, że można go emulować w obiektach OO z obiektami zamkniętymi / funkcyjnymi itp., Ale różnica polega na tym, że cały język i biblioteka standardowa zostały zaprojektowane tak, aby pomóc Ci pisać kod w ten sposób. Na przykład sekwencje Clojure są leniwe , więc mogą być nieskończenie długie, w przeciwieństwie do ArrayLists lub HashMaps w C # / Java. To sprawia, że niektóre algorytmy są znacznie łatwiejsze do wyrażenia, np. Poniższe implementuje nieskończoną listę liczb fibonacci:
(def fibs (lazy-cat '(0 1) (map + fibs (drop 1 fibs))))
Pisanie dynamiczne - Lisps zwykle znajduje się na „dynamicznym” końcu spektrum funkcjonalnego języka programowania (w przeciwieństwie do języków takich jak Haskell z bardzo mocną i piękną koncepcją typów statycznych). Ma to zarówno zalety, jak i wady, ale twierdzę, że języki dynamiczne sprzyjają wydajności programowania ze względu na większą elastyczność. To dynamiczne pisanie wiąże się z niewielkim kosztem wydajności, ale jeśli Ci to przeszkadza, zawsze możesz dodać wskazówki do kodu, aby uzyskać pisanie statyczne. Zasadniczo - domyślnie zyskujesz wygodę i wydajność, jeśli chcesz wykonać dodatkową pracę, aby ją uzyskać.
Interaktywny rozwój - tak naprawdę myślę, że można uzyskać REPL dla C # 4.0, ale w Lisp jest to standardowa praktyka, a nie nowość. W ogóle nie musisz wykonywać cyklu kompilacji / kompilacji / testowania - piszesz kod bezpośrednio w działającym środowisku, a dopiero później kopiujesz go do plików źródłowych. Uczy cię zupełnie innego sposobu kodowania, w którym natychmiast widzisz wyniki i iteracyjnie udoskonalasz swoje rozwiązanie.
Kilka krótkich komentarzy na temat rzeczy, które widzisz jako „brakujące”:
Osobiście uważam zalety Clojure / Lisp za dość przekonujące i nie planuję powrotu do C # / Java (poza tym, co muszę pożyczyć wszystkie przydatne biblioteki!).
Jednak zajęło mi to kilka miesięcy, aby naprawdę się tym zająć. Jeśli naprawdę interesuje Cię nauka Lisp / Clojure jako pouczającej nauki, nic nie zastąpi nurkowania i zmuszania się do wdrożenia kilku rzeczy .....
źródło
C # ma wiele funkcji, ale miks wydaje się inny. To, że jakiś język ma jakąś funkcję, nie oznacza, że jest łatwy w użyciu, paradygmatyczny i dobrze zintegrowany z resztą języka.
Common Lisp ma tę mieszankę:
Teraz inne języki, takie jak C #, również to mają. Ale często nie z takim samym naciskiem.
C # ma
Nie pomaga to, że C # ma na przykład funkcje manipulacji kodem, jeśli nie są one powszechnie używane, jak w Lisp. W Lisp nie znajdziesz wielu programów, które nie wykorzystują makr na wiele prostych i złożonych sposobów. Używanie manipulacji kodami to naprawdę duża funkcja w Lisp. Emulacja niektórych zastosowań jest możliwa w wielu językach, ale nie czyni jej tak istotną jak w Lisp. Zobacz książki takie jak „On Lisp” lub „Practical Common Lisp”.
To samo dotyczy interaktywnego programowania. Jeśli opracujesz duży system CAD, większość prac programistycznych będzie interaktywna z edytora i REPL - bezpośrednio do działającej aplikacji CAD. Możesz emulować wiele z nich w języku C # lub innych językach - często przez implementację języka rozszerzenia. Dla Lisp głupio byłoby zrestartować rozwijaną aplikację CAD w celu dodania zmian. System CAD zawierałby wtedy również ulepszoną Lisp, która dodałaby funkcje językowe (w postaci makr i opisów symbolicznych) do opisywania obiektów CAD i ich relacji (części, zawartości, ...). Można robić podobne rzeczy w C #, ale w Lisp jest to domyślny styl programowania.
Jeśli tworzysz złożone oprogramowanie za pomocą interaktywnego procesu programistycznego lub twoje oprogramowanie ma znaczną część symboliczną (meta programowanie, inne paradygmaty, algebra komputerowa, kompozycja muzyki, planowanie, ...), Lisp może być dla Ciebie.
Jeśli pracujesz w środowisku Windows (ja nie), to C # ma tę przewagę, ponieważ jest głównym językiem programowania Microsoft. Microsoft nie obsługuje żadnej formy Lisp.
Ty piszesz:
Common Lisp nie dołącza przestrzeni nazw do klas. Przestrzenie nazw są dostarczane przez pakiety symboli i są niezależne od klas. Jeśli używasz CLOS, musisz zmienić orientację swojego podejścia do programowania obiektowego.
Użyj kompilatora Lisp. Informuje o nieznanych zmiennych, może mieć zalecenia dotyczące stylu (w zależności od zastosowanego kompilatora), daje wskazówki dotyczące wydajności, ... Kompilator jest naciśnięty klawisz.
Komercyjne Lisps, takie jak LispWorks i Allegro CL, oferują podobne rzeczy pod Windows.
Komercyjne Lisps, takie jak LispWorks i Allegro CL, oferują to wszystko pod Windows.
źródło
Rainier Joswig powiedział to najlepiej.
Dla mnie potęgi Lisp nie można zrozumieć, patrząc tylko na listę funkcji Lisp. W przypadku Lisp, aw szczególności Common Lisp, całość jest większa niż suma części. To nie tylko REPL plus wyrażenia s plus makra plus wieloskładowa wysyłka plus zamknięcia plus pakiety plus CLOS plus restarty plus X, Y i Z. To wszystko jest genialnie zintegrowane. To codzienne doświadczenie, które bardzo różni się od codziennego korzystania z innych języków programowania.
Lisp wygląda inaczej, jest używany inaczej, podczas programowania podchodzi się do programowania w inny sposób. Jest elegancki, kompletny, duży i bezproblemowy. Nie jest pozbawione własnych wad i pecadilloes. Z pewnością istnieją porównania z innymi językami, które mogą być wykonane tam, gdzie mogą się nie pojawić. Ale w żadnym wypadku Lisp nie jest tylko językiem X plus REPL i makrami, ani językiem Y plus wysyłanie wielu metod i restartowanie.
źródło
Zazwyczaj makro należy stosować do czegoś, czego nie można wyrazić jako wywołanie funkcji. Nie wiem, czy istnieje warunek podobny do przełącznika w C # (podobny do tego, co istnieje w C, jak przełącznik (formularz) {przypadek ...}), ale załóżmy, że ma i ma podobne ograniczenia ( wymagające typu integralnego).
Teraz załóżmy dalej, że chcesz coś, co wygląda tak, ale wywołuje ciągi. W lisp (cóż, szczególnie w Common Lisp i prawdopodobnie w innych), to jest „tylko” makro z dala, a jeśli ograniczysz użytkownika do posiadania stałych ciągów (prawdopodobnie nie uciążliwych) do dopasowania, możesz (czas kompilacji) obliczyć minimalna sekwencja testów w celu ustalenia, który przypadek pasuje (lub użyj tabeli skrótów, przechowując zamknięcia).
Chociaż może być możliwe wyrażenie tego jako funkcji (ciąg porównania, lista przypadków i zamknięć do wykonania), prawdopodobnie nie wyglądałby jak „normalny kod” i to tam makra lisp mają wyraźną wygraną. Prawdopodobnie używany sporo makra lub specjalne formy taht są makro-jak, jak
defun
,defmacro
,setf
,cond
,loop
i wiele innych.źródło
To jest najlepszy artykuł wyjaśniający, jaki znalazłem, który zabiera czytelnika z powierzchni rzecznictwa Lisp, aby pokazać niektóre głębsze implikacje zastosowanych pojęć:
http://www.defmacro.org/ramblings/lisp.html
Pamiętam, jak czytałem gdzie indziej taki pomysł: inne języki przyjęły różne funkcje Lisp; wyrzucanie elementów bezużytecznych, dynamiczne pisanie, anonimowe funkcje, zamknięcia w celu uzyskania lepszego C ++ / C / Algol. Jednak, aby uzyskać pełną moc Lisp, potrzebujesz wszystkich jego podstawowych funkcji, w tym momencie masz inny dialekt Lisp, odrębnej rodziny języków, która istnieje w tej czy innej formie od ponad 50 lat.
źródło