Dlaczego niektóre funkcjonalne języki programowania wykorzystują przestrzeń dla aplikacji funkcji?

34

Po zapoznaniu się z niektórymi językami programowania funkcjonalnego zawsze zastanawiałem się, dlaczego niektóre języki fp używają jednego lub więcej białych znaków do zastosowania funkcji (i definicji), podczas gdy większość (wszystkie?) Języków imperatywnych / obiektowych używa nawiasów, co wydaje się być bardziej matematycznym sposobem. Myślę też, że ten drugi styl jest znacznie bardziej wyraźny i czytelny niż bez parens.

Jeśli więc mamy funkcję f (x) = x², istnieją dwie możliwości jej wywołania:

  • FP: f x

    Przykłady:

    • ML, Ocaml, F #
    • Haskell
    • LISP, Schemat (jakoś)
  • Inne niż FP: f(x)

    Przykłady:

    • Prawie wszystkie języki rozkazujące (wiem, zobacz komentarze / odpowiedzi)
    • Erlang
    • Scala (dopuszcza także „notację operatora” dla pojedynczych argumentów)

Jakie są przyczyny „pomijania” nawiasów?

pvorb
źródło
9
Żeby było bardziej mylące, mam na myśli zwięzłe .
Den
11
@Den, jeśli nauczysz się haskell, składnia będzie jedną z najmniej mylących rzeczy: p
Simon Bergot
5
Czy zdajesz sobie sprawę z ironii mówiąc, że użycie nawiasów byłoby bardziej matematyczne, a następnie użycie funkcji wykładniczej jako przykładu?
Jörg W Mittag
12
@Den wyrządzasz sobie krzywdę, odrzucając język ze względu na jego składnię. Na pewno nie miałeś problemów z nauką XML lub SQL (nie są to języki ogólnego przeznaczenia, ale definiują one własną składnię).
Simon Bergot,
7
Chciałbym zaznaczyć, że skrypty powłoki (np. Bash) generalnie nie używają parametrów do wywoływania poleceń. Program PowerShell ma najgorszy z obu światów, ponieważ funkcje są deklarowane w nawiasach i wywoływane bez nich.
Kris Harper

Odpowiedzi:

59

który wydaje się być bardziej matematyczny

języki funkcjonalne inspirowane są rachunkiem lambda . W tym polu nawiasy nie są używane do zastosowania funkcji.

Myślę też, że ten drugi styl jest znacznie bardziej wyraźny i czytelny niż bez parens.

Czytelność zależy od obserwatora. Nie jesteś przyzwyczajony do czytania. To trochę jak operatory matematyczne. Jeśli rozumiesz skojarzenie, potrzebujesz tylko kilku parens, aby wyjaśnić strukturę swojego wyrażenia. Często ich nie potrzebujesz.

Curry jest również dobrym powodem do zastosowania tej konwencji. W haskell możesz zdefiniować:

add :: Int -> Int -> Int
add x y = x + y

x = add 5 6 -- x == 11
f = add 5
y = f 6 -- y == 11
z = ((add 5) 6) -- explicit parentheses; z == 11

W przypadku parens można zastosować dwie konwencje: f(5, 6)(nie curry) lub f(5)(6)(curry). Składnia haskell pomaga przyzwyczaić się do koncepcji curry. Nadal możesz używać wersji bez curry, ale bardziej bolesne jest używanie jej z kombinatorami

add' :: (Int, Int) -> Int
add' (x, y) = x + y
u = add'(5, 6) -- just like other languages
l = [1, 2, 3]
l1 = map (add 5) l -- [6, 7, 8]
l2 = map (\x -> add'(5, x)) l -- like other languages

Zauważ, jak druga wersja zmusza cię do zarejestrowania x jako zmiennej i że podwyrażenie jest funkcją, która bierze liczbę całkowitą i dodaje do niej 5? Wersja curry jest znacznie lżejsza, ale przez wielu uważana za bardziej czytelną.

Programy Haskell w szerokim zakresie wykorzystują częściową aplikację i kombinatory do definiowania i komponowania abstrakcji, więc nie jest to zabawkowy przykład. Dobry interfejs funkcyjny to taki, w którym kolejność parametrów zapewnia przyjazne korzystanie z curry.

Kolejna kwestia: należy wywołać funkcję bez parametrów f(). W Haskell, ponieważ manipulujesz tylko niezmiennymi leniwymi wartościami, po prostu piszesz fi traktujesz to jako wartość, która będzie musiała wykonać pewne obliczenia, gdy zajdzie taka potrzeba. Ponieważ jego ocena nie spowoduje żadnych skutków ubocznych, nie ma sensu mieć innej notacji dla funkcji bez parametrów i jej zwracanej wartości.

Istnieją również inne konwencje dotyczące zastosowania funkcji:

  • Lisp: (fx) - przedrostek z zewnętrznymi nawiasami
  • Dalej: xf - postfiks
Simon Bergot
źródło
7
Drobny nitpick: terminami są „curry” i „curry”, a nie „curryfikacja” i „curry”.
Doval
„nie curry” czym jest funkcja „nie curry” w haskell?
Ven
3
@ user1737909 Funkcja, która przyjmuje krotkę jako argument.
Doval
1
@Doval Właściwie powinno to być „schönfinkeling” i „schönfinkeled”. Ale cóż, historia zawsze określa zwycięzcę z mocą wsteczną.
Profpatsch
Myślenia o składni currying nawiasy, coś f(a, ,b)lub add(a, )czy add(, b)wydaje się bardziej elastyczne domyślnie.
fresnel
25

Podstawową ideą jest uczynienie najważniejszej operacji (aplikacji funkcyjnej) najłatwiejszą do odczytania i najłatwiejszą do napisania. Spacja jest bardzo mało czytelna i bardzo łatwa do pisania.

Pamiętaj, że nie jest to specyficzne dla języków funkcjonalnych, np. W Smalltalk , jednym z pierwszych języków OO i językach przez niego inspirowanych ( Self , Newspeak , Objective-C), ale także w Io i językach przez niego inspirowanych (Ioke, Seph), biały znak służy do wywołań metod.

W stylu smalltalk:

anArray sort
anArray add: 2
aString replace: "a" with: "b"

(W tym drugim przypadku nazwa metody to replace:with:.)

Styl Io:

anArray sort
anArray add(2)
aString replace("a", "b")

Scala dopuszcza również spacje dla wywołań metod:

foo bar(baz)

I pomijając nawiasy, jeśli jest tylko jeden argument:

foo bar baz

Ruby pozwala również pominąć nawiasy:

an_array.sort
an_array.add 2
a_string.replace "a", "b"

używając nawiasów, co wydaje się być bardziej matematycznym sposobem

Nie całkiem:

f(x)
sin x
x²
|x|
x!
x + y
xy
½

Notacja matematyczna ewoluowała przez długi czas w okropnie niespójny sposób.

Jeśli w ogóle, języki funkcjonalne czerpią inspirację z rachunku λ, w którym aplikacja funkcji jest pisana za pomocą białych znaków.

Jörg W Mittag
źródło
3
Istnieje również argument za oszczędnością edycji. W szczególności języki funkcjonalne zazwyczaj bezpośrednio obsługują kompozycję. Sensowne jest umieszczanie nawiasów wokół funkcji zamiast argumentu. Musisz to zrobić tylko „raz”: (e. F. G) x w przeciwieństwie do e (f (g (x))). Ponadto, przynajmniej Haskell nie powstrzymałby jednego przed zrobieniem f (x) zamiast f x. To po prostu nie idiomatyczne.
nomen
18

Nawias dla funkcji jest tylko jednym z wielu siodeł, które nam zostawił Euler. Jak wszystko inne matematyka wymaga konwencji, gdy istnieje kilka sposobów na zrobienie czegoś. Jeśli twoje wykształcenie matematyczne obejmuje jedynie przedmiot niematerialny na uniwersytecie, prawdopodobnie nie znasz zbyt wielu dziedzin, w których zastosowanie funkcji odbywa się szczęśliwie bez żadnej z tych nonsensownych nawiasów (np. Geometria różniczkowa, algebra abstrakcyjna). I nawet nie wspominasz o funkcjach, które są stosowane jako poprawki (prawie we wszystkich językach), „outfix”, jak przyjmowanie norm, lub schematycznie (Befunge, Topologia algebraiczna).

W odpowiedzi powiedziałbym, że wynika to ze znacznie większego odsetka programistów funkcjonalnych i projektantów językowych posiadających bogate wykształcenie matematyczne. Von Neumann mówi: „Młody człowieku, w matematyce nie rozumiesz rzeczy. Po prostu się do nich przyzwyczajasz.”, Z pewnością dotyczy to notacji.

Sean D.
źródło
7
Nawet mnie nie zaczynaj na f(x)vs. sin xvs. vs. |x|vs. x!vs. x + yvs. xyvs. ½vs. suma vs. całka vs.…
Jörg W Mittag
2
+1 za cytat von Neumana. +1 za resztę odpowiedzi, ale nie mogę dać +2. :(
pvorb
2
Wyczuwam tu odrobinę elitaryzmu; Kwestionuję neutralność „projektantów funkcjonalnych języków programowania są lepiej wykształceni” .
CaptainCodeman
7
@CaptainCodeman Mówi, że jest bardziej wykształcony z matematyki, z czym się zgadzam. Przynajmniej w przypadku Haskell można zauważyć, że oba pojęcia mają bardziej matematyczny charakter, a społeczność jest również bardziej matematyczna. Nie są bardziej inteligentni, tylko lepiej wykształceni w matematyce.
Paul
5
@CaptainCodeman Nie, ponieważ nigdy nie sugerował, że są ogólnie bardziej wykształceni, tylko bardziej wykształceni w matematyce. Dokładnie tak powiedział: „szerokie wykształcenie matematyczne”. :)
Paul
8

Chociaż w odpowiedzi Simona jest wiele prawdy, myślę, że istnieje również bardziej praktyczny powód. Natura programowania funkcjonalnego zwykle generuje znacznie więcej nawiasów niż programowanie imperatywne, ze względu na tworzenie łańcuchów funkcji i skład. Te wzorce tworzenia łańcuchów i kompozycji zazwyczaj są w stanie być jednoznacznie przedstawione bez nawiasów.

Najważniejsze jest to, że wszystkie te nawiasy są denerwujące do czytania i śledzenia. Jest to prawdopodobnie najważniejszy powód, dla którego LISP nie jest bardziej popularny. Więc jeśli uda ci się uzyskać moc programowania funkcjonalnego bez uciążliwości związanych z nadmiarem nawiasów, myślę, że projektanci języków będą dążyli w tym kierunku. W końcu projektanci języków są również użytkownikami języków.

Nawet Scala pozwala programiście pomijać nawiasy w pewnych okolicznościach, które zdarzają się dość często przy programowaniu w funkcjonalnym stylu, dzięki czemu uzyskuje się to, co najlepsze z obu światów. Na przykład:

val message = line split "," map (_.toByte)

Pareny na końcu są niezbędne do skojarzenia, a pozostałe są pomijane (podobnie jak kropki). Pisanie w ten sposób podkreśla połączony charakter wykonywanych operacji. Być może nie jest to konieczne dla programisty, ale dla programisty funkcjonalnego pisanie w ten sposób jest bardzo naturalne i płynne, bez konieczności zatrzymywania się i wstawiania składni, która nie dodaje żadnego znaczenia programowi, ale jest tylko po to, aby uszczęśliwić kompilator .

Karl Bielefeldt
źródło
2
„Najważniejsze jest to, że wszystkie te nawiasy są denerwujące do czytania i śledzenia. Prawdopodobnie jest to jeden z głównych powodów, dla których LISP nie jest bardziej popularny.”: Nie sądzę, aby nawiasy były powodem, dla którego Lisp nie był popularny: nie sądzę są szczególnie trudne do odczytania, a inne języki mają znacznie bardziej niezręczną składnię.
Giorgio
2
Składnia LISP w dużej mierze rekompensuje irytujące nawiasy z jego bardzo wysokim stopniem ortogonalności. Oznacza to po prostu, że ma inne funkcje odkupienia, które czynią go nadal użytecznym, nie że pareny nie są denerwujące. Rozumiem, że nie wszyscy tak się czują, ale zdecydowana większość programistów, których spotkałam, robi tak.
Karl Bielefeldt
„Lost In Stupid Parentheses” to mój ulubiony backronym LISP. Komputery mogą także w humanitarny sposób określać bloki kodu i rezygnować z nadmiarowości, tak jak w językach takich jak Wisp .
Cees Timmerman,
4

Oba przesłanki są błędne.

  • Te języki funkcjonalne nie zajmują miejsca na aplikacje funkcyjne. To, co robią, to po prostu analizuje każde wyrażenie występujące po funkcji jako argument.

    GHCi> f 3
    4
    GHCi> f (3)
    4
    GHCi> (f) 3
    4

    Oczywiście jeśli używasz ani przestrzeń, ani parens to zazwyczaj nie działa, po prostu dlatego, że ekspresja nie jest prawidłowo tokenised

    GHCi> f3

    <‌interactive>: 7: 1: Poza
        zakresem: „f3”
        Być może miałeś na myśli „f” (wiersz 2)

    Ale jeśli te języki byłyby ograniczone do 1-znakowych nazw zmiennych, to też by działały.

  • Konwencje matematyczne zwykle nie wymagają nawiasów do zastosowania funkcji. Matematycy często nie tylko piszą sin x - w przypadku funkcji liniowych (zwykle wtedy nazywanych operatorami liniowymi) bardzo często zapisuje się argument bez parens, być może dlatego, że (podobnie jak każda funkcja w programowaniu funkcjonalnym) funkcje liniowe są często obsługiwane bez bezpośredniego dostarczania argument.

Tak naprawdę twoje pytanie sprowadza się do: dlaczego niektóre języki wymagają nawiasów do zastosowania funkcji? Najwyraźniej niektórzy ludzie, tacy jak ty, uważają to za bardziej czytelne. Zdecydowanie się z tym nie zgadzam: jedna para parenów jest miła, ale gdy tylko dwie z nich zostaną zagnieżdżone, zaczyna się mylić. Kod Lisp pokazuje to najlepiej i prawie wszystkie języki funkcjonalne wyglądałyby podobnie zagracone, gdybyś potrzebował wszędzie parens. Nie zdarza się to tak często w imperatywnych językach, ale głównie dlatego, że języki te nie są wystarczająco wyraziste, aby napisać zwięzłe, jasne jednostronne linie.

po lewej stronie
źródło
Mam pytanie, pytanie nie jest idealne. :) Wniosek do narysowania jest następujący: „Nawiasy nie skalują się”.
pvorb
2
Nie są to również języki niefunkcjonalne, które jednogłośnie wymagają nawiasów; dla kilku przykładów Smalltalk ma object method: args, Cel C ma [object method: args], a Ruby i Perl pozwalają na pomijanie nawiasów w wywołaniach funkcji i metod, wymagając tylko od nich rozwiązania niejednoznaczności.
hobbs
1

Małe wyjaśnienie historyczne: pierwotna notacja w matematyce nie zawierała nawiasów !

Notację fx dla dowolnej funkcji x wprowadził Johan Bernoulli około 1700 roku krótko po tym, jak Leibniz zaczął mówić o funkcjach (faktycznie Bernoulli napisał x ). Ale wcześniej wiele osób używało już notacji takich jak sin x lub lx (logarytm x ) dla określonych funkcji x , bez nawiasów. Podejrzewam, że właśnie dlatego wielu ludzi wciąż pisze sin x zamiast sin (x) .

Euler, który był uczniem Bernoulliego, przyjęty Bernoulliego φ x i zmienił się w greckiej litery alfabetu łacińskiego, pisanie fx . Później zauważył, że łatwo pomylić f z „ilością” i wierzyć, że fx był produktem, więc zaczął pisać f: x . Stąd notacja f (x) nie wynika z Eulera. Oczywiście Euler potrzebował nawiasów z tego samego powodu, dla którego nadal potrzebujemy nawiasów w funkcjonalnych językach programowania: aby odróżnić na przykład (fx) + y od f (x + y) . Podejrzewam, że ludzie po Eulerze przyjęli nawias jako domyślny, ponieważ widzieli głównie Eulera piszącego wyrażenia złożone, takie jak f (ax + b). Rzadko pisał tylko fx .

Dowiedz się więcej na ten temat: Czy Euler kiedykolwiek pisał f (x) w nawiasie? .

Nie wiem, czy projektanci funkcjonalnych języków programowania czy wynalazcy rachunku lambda znali historyczne korzenie ich zapisu. Osobiście uważam, że notacja bez nawiasów jest bardziej naturalna.

Michael Bächtold
źródło