Najczystsze funkcjonalne języki programowania? [Zamknięte]

20

Jestem zainteresowany lepszą nauką programowania funkcjonalnego. Aby to zrobić, wydaje się oczywiste, że powinienem zmusić się do użycia najczystszego możliwego funkcjonalnego języka programowania. Dlatego też mniej więcej tutaj proszę o uporządkowanie funkcjonalnych języków programowania zgodnie z ich czystością.

Wydaje mi się, że praktyczniej byłoby nauczyć się Lisp lub Clojure (lub Scheme, Scala itp.), Ale z powodu tego, co ostatnio słyszałem, Haskell byłby bardzo trudny do nauczenia kogoś, kto nauczałby zasad programowania funkcjonalnego. Nie jestem jeszcze tego pewien, więc pytam: który jest najczystszym funkcjonalnym językiem programowania? Kolejność byłaby świetna, gdyby kilku walczyło o wspaniały tytuł najczystszego funkcjonalnego języka programowania.

Joanis
źródło
2
Nauczyłem się Mirandy na uniwersytecie, więc jestem uprzedzony, ale sugerowałbym Haskel każdemu, kto chce zanurzyć się w funkcjonalnym języku bez rozpraszania nieczystości . * 8 ')
Mark Booth
2
Po zakończeniu nauki programowania funkcjonalnego powinieneś nauczyć się programowania statycznego z wykorzystaniem ekspresyjnego systemu typów. W połączonej kategorii (zarówno funkcjonalnej, jak i maszynowej) sugeruję: Coq> Haskell> OCaml> Scala> inne. Istnieje kilka mniej popularnych alternatyw, które pasują między Coq i Haskell (jak Epigram i Agda). Haskell tęskni za ekspresyjnym systemem modułów OCamla.
lukstafi

Odpowiedzi:

28

Nie ma skali do oceny stopnia czystości języków funkcjonalnych. Jeśli język dopuszcza skutki uboczne, jest nieczysty, w przeciwnym razie jest czysty. Zgodnie z tą definicją Haskell, Mercury, Clean itp. Są czystymi językami funkcjonalnymi; podczas gdy Scala, Clojure, F #, OCaml itp. są nieczyste.

EDYCJA: Może powinienem to sformułować jako „jeśli język nie dopuszcza skutków ubocznych bez powiadomienia systemu typów , jest czysty. W przeciwnym razie jest nieczysty”.

missingfaktor
źródło
6
Niezupełnie: Haskell dopuszcza skutki uboczne ( IOmonada). Po prostu kod powodujący skutki uboczne jest wyraźnie oznaczony jako taki. Nie sądzę, że w ogóle warto mówić o czystych / nieczystych językach (Haskell pozwala programować imperatywnie! Gasp!), Ale nadal może być przydatne mówienie o fragmentach kodu (funkcja, moduł, program, cokolwiek) jako czystych /zanieczyszczony.
Frank Shearar,
8
@Frank: Tak, Haskell dopuszcza skutki uboczne, ale nie tylko zaznacza kod powodujący skutki uboczne. Utrzymuje także przejrzystość referencyjną, nawet przy użyciu kodu powodującego skutki uboczne, i właśnie to czyni czystym - przynajmniej dzięki definicji czystości wielu ludzi. Oczywiście, że nie pasuje to do definicji brakującego faktora „niedozwolone efekty uboczne”.
sepp2k,
4
@Frank Shearar, ale warto rozmawiać w taki sposób, ponieważ IOmonada jest czysta. Jest to biblioteka środowiska wykonawczego, która nie jest twoim kodem, ponieważ twoja mainfunkcja jest w zasadzie ogromnym transformatorem stanu. (Coś jak main :: World -> Worldza kulisami)
alternatywny
1
Mój język ma również przejrzystość referencyjną. Po prostu piszesz program = "some C code", a środowisko wykonawcze zajmuje się kodem C. :-)
Daniel Lubarov
3
Ponieważ drukowanie na ekranie jest efektem ubocznym, naprawdę czyste programy funkcjonalne są dość nudne.
15

Ponieważ Twoim celem jest uczenie się, a nie pisanie programów per se, nie możesz uzyskać czystszego niż Rachunek Lambda .

Rachunek Lambda istniał już przed wynalezieniem komputerów. Pracowało nad tym kilku wykwalifikowanych logików, aby dowiedzieć się, jak wykonać odejmowanie (przez pewien czas uważano, że możliwe jest tylko dodawanie i mnożenie).

Nauczenie się, w jaki sposób booleany i liczby, i które ifmożna wymyślić z pozornie nic, nie zwiększy ilości paliwa w twoim zbiorniku, ale sprawi, że twój zbiornik będzie znacznie większy.

Macneil
źródło
To prawda, że ​​prawdopodobnie nie ma nic bardziej czystego, ale przekraczamy nieco barierę matematyczną. Nadal chcę ćwiczyć programowanie i uczyć się nowego języka, wchłaniając zasady funkcjonalne. Zgadzam się jednak, że interesujące może być badanie rachunku lambda tylko w celu lepszego zrozumienia podstaw paradygmatu funkcjonalnego (i posiadania większego czołgu).
Joanis,
2
Pisanie dowodu to pisanie programu, a pisanie programu to pisanie dowodu. Kiedy dowiesz się o izomorfizmie Curry-Howarda, zdasz sobie sprawę, że przekroczyłeś tę barierę matematyczną dziesięć tysięcy linii temu.
Macneil,
1
Nie ma prostej reprezentacji -1.
Daniel Lubarov,
2
@Mason Wheeler: Rachunek λ sam w sobie nie ma liczb ani żadnych danych poza abstrakcjami lambda. O ile nie podano inaczej, każdy, kto mówi o liczbach w rachunku λ, prawdopodobnie oznacza kodowanie przez Kościół liczb naturalnych , gdzie liczba n jest reprezentowana przez składową funkcji n-krotnie. To powiedziawszy, jestem wątpliwe było , że dużo walka dowiedzieć się, odejmowanie, gdy ktoś rzeczywiście próbował; Rozpracowałem to sam po południu, mając tylko definicję dodawania i wiedząc, że odejmowanie jest możliwe.
CA McCann,
1
Odejmowanie za pomocą cyfr kościelnych jest łatwe, gdy masz rozkład według przypadków. Budowanie całego (pochodnego) rachunku różniczkowego do obsługi liczb ujemnych, a raczej więcej pracy.
Donal Fellows
6

Zanieczyszczone języki tak naprawdę nie różnią się zasadniczo od bardziej znanych języków imperatywnych, zwłaszcza teraz, gdy skopiowano wiele sztuczek funkcjonalnych. Odmienny jest styl - sposób rozwiązywania problemów.

Niezależnie od tego, czy liczysz Haskella jako czysty, czy monadę IO jako nieczystość, styl Haskell jest ekstremalną formą tego stylu i warto się go nauczyć.

Monada IO Haskell wywodzi się z matematycznej teorii (oczywiście) monad. Jednak w przypadku programistów imperatywnych myślę, że powrót do monad ma większy sens.

Faza pierwsza - czysty język funkcjonalny może łatwo zwrócić dużą wartość ciągu. Ten duży ciąg może być kodem źródłowym programu imperatywnego, pochodzącego w sposób czysto funkcjonalny od niektórych parametrów określających wymagania. Następnie można zbudować kompilator „wyższego poziomu”, który uruchamia generator kodu, a następnie automatycznie podaje wygenerowany kod do kompilatora języka imperatywnego.

Faza druga - zamiast generować tekstowy kod źródłowy, generujesz silnie wpisane abstrakcyjne drzewo składniowe. Kompilator w języku imperatywnym jest wchłaniany do kompilatora „wyższego poziomu” i akceptuje AST bezpośrednio jako kod źródłowy. Jest to o wiele bliższe temu, co robi Haskell.

Jednak nadal jest to niezręczne. Na przykład masz dwa różne rodzaje funkcji - te oceniane podczas fazy generowania kodu i wykonywane podczas uruchamiania generowanego programu. To trochę jak rozróżnienie między funkcjami i szablonami w C ++.

Tak więc, dla fazy 3, uczyń obie te same - ta sama funkcja o tej samej składni może być częściowo oceniana podczas „generowania kodu”, lub w pełni oceniana, lub w ogóle nie oceniana. Ponadto należy odrzucić wszystkie zapętlone konstrukty AST na rzecz rekurencji. W rzeczywistości odrzuć ideę węzłów AST jako specjalnego rodzaju danych w ogóle - nie posiadaj węzłów AST o „wartości dosłownej”, tylko wartości itp.

To właśnie robi monada IO - operator bind jest sposobem komponowania „akcji” w celu tworzenia programów. To nic specjalnego - tylko funkcja. Wiele wyrażeń i funkcji można ocenić podczas „generowania kodu”, ale te, które zależą od efektów ubocznych I / O, muszą mieć ocenę opóźnioną do czasu wykonania - nie przez żadną specjalną regułę, ale jako naturalną konsekwencję zależności danych w wyrażenia.

Generalnie monady są po prostu uogólnieniem - mają ten sam interfejs, ale implementują operacje abstrakcyjne inaczej, więc zamiast ewaluować do opisu kodu imperatywnego, oceniają na coś innego. Posiadanie tego samego interfejsu oznacza, że ​​istnieje kilka rzeczy, które możesz zrobić monadom, nie dbając o to, która monada, co okazuje się przydatne.

Ten opis bez wątpienia spowoduje eksplozję purystycznych głów, ale dla mnie wyjaśnia kilka prawdziwych powodów, dla których Haskell jest interesujący. Zaciera granicę między programowaniem a metaprogramowaniem i wykorzystuje narzędzia programowania funkcjonalnego do ponownego tworzenia imperatywnego programowania bez potrzeby specjalnej składni.

Krytykę, jaką mam szablony C ++, polega na tym, że są one rodzajem zepsutego czysto funkcjonalnego sublanguage w języku imperatywnym - aby ocenić tę samą podstawową funkcję w czasie kompilacji, a nie w czasie wykonywania, trzeba ją ponownie zaimplementować w zupełnie innym stylu kodowania. W Haskell, podczas gdy zanieczyszczenie musi być oznaczone jako takie w swoim typie, dokładnie ta sama funkcja może być oceniana zarówno w sensie metaprogramowania, jak i w trybie niemetaprogramowania w czasie wykonywania w tym samym programie - nie ma twardego wiersza między programowaniem a metaprogramowaniem.

To powiedziawszy, istnieją pewne rzeczy związane z metaprogramowaniem, których standardowy Haskell nie potrafi, głównie dlatego, że typy (i może kilka innych rzeczy) nie są pierwszorzędnymi wartościami. Istnieją jednak warianty językowe, które próbują rozwiązać ten problem.

Wiele rzeczy, które powiedziałem o Haskell, można zastosować w nieczystych językach funkcjonalnych - a czasem nawet w imperatywnych. Haskell jest inny, ponieważ nie masz innego wyjścia, jak przyjąć to podejście - w zasadzie zmusza Cię do nauki tego stylu pracy. Możesz „pisać C w ML”, ale nie możesz „pisać C w Haskell” - przynajmniej nie bez wiedzy o tym, co się dzieje pod maską.

Steve314
źródło
Dziękuję Ci! Zastanawiałem się, czy ogólność szablonów C ++ mogłaby zostać ujednolicona w samych funkcjach. Wygląda na to, że robi to Haskell, ale ważną częścią jest to, czy można to zaimplementować w skompilowanym języku, tak aby ten sam silnik tworzący ogólne funkcje z szablonów podczas kompilacji mógł również je oceniać w czasie wykonywania ...
Milind R
5

Osobiście dzielę języki na trzy poziomy czystości funkcjonalnej:

  • Języki czysto funkcjonalne - tj. Te, które traktują cały program jako czystą funkcję i obsługują zmienność wyłącznie poprzez interakcję z środowiskiem wykonawczym - Haskell jest prawdopodobnie kanonicznym przykładem

  • Nieczyste języki funkcjonalne - tj. Te, które podkreślają funkcjonalny styl, ale pozwalają na efekty uboczne.Clojure jest wyraźnie w tej kategorii (pozwala mutować w kontrolowany sposób jako część swojego szkieletu STM), również OCaml lub F #

  • Języki z wieloma paradygmatami - nie są to przede wszystkim języki funkcjonalne, ale mogą obsługiwać styl funkcjonalny za pomocą funkcji pierwszej klasy itp. Scala jest tutaj dobrym przykładem, chciałbym również Common Lisp w tej kategorii, a można nawet dołączyć języki takie jak JavaScript .

W twojej sytuacji proponuję najpierw nauczyć się Haskell, a potem Clojure. Tak właśnie zrobiłem i działało to dla mnie bardzo dobrze! Haskell jest piękny i uczy najczystszych zasad funkcjonalnych, Clojure jest znacznie bardziej pragmatyczny i pomaga ci wiele zrobić, a jednocześnie jest bardzo funkcjonalny w sercu.

Tak naprawdę nie liczę trzeciej kategorii jako języków funkcjonalnych (chociaż po nauce Haskell i Clojure często korzystam z technik funkcjonalnych podczas ich używania!)

mikera
źródło
W ramach bardzo ścisłych ograniczeń nawet C ma możliwości funkcjonalne. (Głównym ograniczeniem jest synteza funkcji w czasie wykonywania, co jest naprawdę przerażające w C, do tego stopnia, że ​​większość ludzi twierdzi, że nie można tego zrobić).
Donal Fellows
2
@Donal Fellows: W miarę jak głupota zmierza w nieskończoność, każdy język kompletny Turinga działa: wystarczy zaimplementować interpreter Lisp w tym języku, a następnie go użyć. :]
CA McCann
Paradygmaty nie mają znaczenia, jeśli weźmie się pod uwagę pogląd, że wszystko jest Turinga kompletne, a zatem może naśladować wszystko inne. Kiedy myślisz o paradygmatach, musisz skupić się na tym, co język czyni idiomatycznym, a co zniechęca / zapobiega. Moim zdaniem, aby liczyć się jako język funkcjonalny, mutacja i skutki uboczne muszą być unidiomatyczne (dla nieczystego FP) lub zabronione (dla czystego FP). Oznacza to, że C nie działa. Nie są też językami paradygmatu, jak Scala.
mikera
@mikera: Uważam, że twoja odpowiedź jest bardzo interesująca: jeśli Scala nie jest funkcjonalna, a jedynie wieloparadygmatyczna, jak oceniasz funkcjonalne funkcje programowania w C ++ 11, C #, a nawet Java (planowane wprowadzenie lambdas)?
Giorgio
@Giorgio: Myślę, że funkcje funkcjonalne we wszystkich wymienionych przez ciebie językach są prawdopodobnie dobrym pomysłem, po prostu nie robią z nich „języków funkcjonalnych”. Albo patrząc na to z przeciwnej strony, fakt, że możesz robić monadyczne operacje IO w stylu imperatywnym, nie czyni z Haskella imperatywnego języka :-). Języki wielu paradygmatów są w zasadzie wynikiem, gdy łączysz funkcje z wielu różnych paradygmatów i nie stawiasz żadnego szczególnego stylu na pierwszym miejscu. Wszystkie IMHO C ++, C # i Java zmierzają w kierunku paradygmatu, ale jeszcze ich nie ma, ponieważ OOP nadal dominuje.
mikera
3

Jeśli czysty język funkcjonalny jest taki, że ma tylko czyste funkcje (procedury, które nie mają skutków ubocznych), to jest to trochę bezcelowe, ponieważ nie może odczytać danych wejściowych ani wyjściowych;)

Bo to jest naprawdę dla uczenia się, myślę, że izolacja nie jest koniecznie droga. Programowanie funkcjonalne jest paradygmatem. Ważne jest, aby zrozumieć, które paradygmaty są odpowiednie dla których problemów, a co ważniejsze, jak najlepiej je połączyć.

Powiem teraz: mody programistyczne są głupie i przynoszą efekt przeciwny do zamierzonego. Liczy się tylko to, że Twój program jest krótki, łatwy do napisania, łatwy w utrzymaniu i działa poprawnie. Jak to osiągnąć, nie ma to nic wspólnego z modami programowania. - Richard Jones

Poza tym, jeśli szukasz „czystości”, możesz rzucić okiem na Pure . Należy jednak pamiętać, że niezwykle łatwa funkcja wywoływania procedur C powoduje, że jest ona funkcjonalnie nieczysta (ale także bardzo wydajna).

back2dos
źródło
3

Nie do końca poważna odpowiedź, ale Unlambda musi być konkurentem. Nie można uzyskać bardziej „czysto funkcjonalnego” niż kombinatory SKI.

Stephen C.
źródło
4
Szaleństwo! Herezja! Jaki rodzaj czystego języka ma absurdy, takie jak kombinatory z efektami ubocznymi? Nie? Nie. Co naprawdę chcesz tu jest Lazy K .
CA McCann,
2

Erlang, Haskell, Scheme, Scala, Clojure i F #

To pytanie prawdopodobnie najlepiej pomóc Ci w poszukiwaniu jak dobrze .

zakurzony programista
źródło
Dzięki, ciekawe pytanie. Zacząłem od PLT-Scheme / Racket, ale jeszcze nie spojrzałem na SICP ... Real World Haskell też wygląda dla mnie całkiem interesująco.
Joanis,
Program zachęca do funkcjonalnego stylu, ale ma set!(między innymi) ...
Daniel Lubarov
Sugeruję OCaml, ponieważ jest to mój ulubiony. I ma system modułowy, którego brakuje Haskell i F #.
lukstafi
@lukstafi może haskell zmieniło się w ciągu ostatnich 3 lat (wiem, że urósł jak szalony), ale zdecydowanie haskell zawiera moduły.
sara
@kai Przez system modułowy rozumiałem moduły parametryzowane przez inne moduły („rachunek lambda” modułów).
lukstafi