Dlaczego większość języków programowania ma specjalne słowa kluczowe lub składnię do deklarowania funkcji? [Zamknięte]


39

Większość języków programowania (zarówno języki dynamiczne, jak i statyczne) mają specjalne słowa kluczowe i / lub składnię, która wygląda znacznie inaczej niż deklarowanie zmiennych dla deklaracji funkcji. Widzę funkcje tak samo, jak deklarowanie innego nazwanego bytu:

Na przykład w Pythonie:

x = 2
y = addOne(x)
def addOne(number): 
  return number + 1

Dlaczego nie:

x = 2
y = addOne(x)
addOne = (number) => 
  return number + 1

Podobnie w języku takim jak Java:

int x = 2;
int y = addOne(x);

int addOne(int x) {
  return x + 1;
}

Dlaczego nie:

int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
  return x + 1;
}

Ta składnia wydaje się bardziej naturalnym sposobem deklarowania czegoś (czy to funkcji, czy zmiennej) i jednego mniej słowa kluczowego, takiego jak deflub functionw niektórych językach. I, IMO, jest bardziej spójny (szukam w tym samym miejscu, aby zrozumieć typ zmiennej lub funkcji) i prawdopodobnie sprawia, że ​​parser / gramatyka jest nieco prostsza do napisania.

Wiem, że bardzo niewiele języków korzysta z tego pomysłu (CoffeeScript, Haskell), ale większość popularnych języków ma specjalną składnię dla funkcji (Java, C ++, Python, JavaScript, C #, PHP, Ruby).

Nawet w Scali, która obsługuje oba sposoby (i ma wnioskowanie o typie), bardziej powszechne jest pisanie:

def addOne(x: Int) = x + 1

Zamiast:

val addOne = (x: Int) => x + 1

IMO, przynajmniej w Scali, jest to prawdopodobnie najłatwiejsza do zrozumienia wersja, ale ten idiom jest rzadko stosowany:

val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1

Pracuję nad własnym językiem zabawek i zastanawiam się, czy są jakieś pułapki, jeśli zaprojektuję taki język w taki sposób i czy istnieją jakieś przyczyny historyczne lub techniczne, że ten wzór nie jest powszechnie stosowany?


9
Przyczyna jest prawdopodobnie historyczna. Ktokolwiek to zrobił, zrobił to w ten sposób, a wszyscy inni skopiowali. Ale wątpię, czy możemy to wiedzieć na pewno.

29
Myślę, że dzieje się tak, ponieważ funkcja lub metoda po prostu nie jest po prostu innym nazwanym bytem . W językach funkcjonalnych są one (można je przekazywać itp.), Ale w innych językach (takich jak Java) funkcja lub metoda jest czymś zupełnie innym niż prosta zmienna i nie może być traktowana jako taka (przyznaję, że Java 8 osłabia to instrukcja), więc sensowne jest definiowanie funkcji / metod w inny sposób, ponieważ zachowują się inaczej.
11684

15
Nie zgadzam się, że twoja propozycja jest bardziej naturalnym sposobem deklarowania funkcji. Naprawdę nie lubię składni Coffeescript, a częścią tego jest składnia deklaracji funkcji. Faktem jest, że jeśli nie jest zepsuty, nie naprawiaj go. Pisanie „def” lub „funkcja” nie jest wielkim problemem i jest o wiele bardziej oczywistym spojrzeniem w porównaniu z niektórymi fantazyjnymi symbolami podobnymi do Perla, które z bezmyślnymi lub zmęczonymi oczami można łatwo pomylić z czymś innym. Jeśli chodzi o całkowicie osobistą notatkę, myślę również, że twoja sugerowana składnia w przykładach wygląda o wiele brzydiej niż obecna.
Roy

22
Dlaczego „=>” nie jest „specjalnym słowem kluczowym lub składnią do deklarowania funkcji”?
TML

11
Nie wiem o tobie, ale dla mnie (int => int) addOne = (x) => {jest o wiele bardziej „specjalny” i „złożony” niż int addOne(int) {...
Bogdan Alexandru

Odpowiedzi:


48

Myślę, że powodem jest to, że większość popularnych języków pochodzi z rodziny języków C lub była pod ich wpływem, w przeciwieństwie do języków funkcjonalnych i ich źródła, rachunku lambda.

W tych językach funkcje nie są tylko kolejną wartością:

  • W C ++, C # i Javie możesz przeciążać funkcje: możesz mieć dwie funkcje o tej samej nazwie, ale z inną sygnaturą.
  • W C, C ++, C # i Java można mieć wartości reprezentujące funkcje, ale wszystkie wskaźniki funkcji, funktory, delegaty i interfejsy funkcjonalne różnią się od samych funkcji. Częściowo dlatego, że większość z nich nie jest właściwie tylko funkcjami, są one funkcją wraz z pewnym (zmiennym) stanem.
  • Zmienne są zmienne domyślnie (trzeba użyć const, readonlylub finalzabronić mutację), ale funkcje nie mogą być przypisane.
  • Z bardziej technicznego punktu widzenia kod (który składa się z funkcji) i dane są osobne. Zazwyczaj zajmują one różne części pamięci i są dostępne w różny sposób: kod jest ładowany raz, a następnie tylko wykonywany (ale nie odczytywany ani zapisywany), podczas gdy dane są często stale przydzielane i zwalniane oraz są zapisywane i odczytywane, ale nigdy nie są wykonywane.

    A ponieważ C miał być „bliski metalowi”, sensowne jest także odzwierciedlenie tego rozróżnienia w składni języka.

  • Podejście „funkcja jest po prostu wartością”, które stanowi podstawę programowania funkcjonalnego, stosunkowo niedawno zyskało popularność we wspólnych językach, o czym świadczy późne wprowadzenie lambda w C ++, C # i Javie (2011, 2007, 2014).


12
C # miał anonimowe funkcje - które są po prostu lambdami bez wnioskowania o typie i niezgrabną składnią - od 2005 roku.
Eric Lippert

2
„Z bardziej technicznego punktu widzenia kod (który składa się z funkcji) i dane są oddzielne” - niektóre języki, najlepiej świadomie LISP, nie dokonują tego rozdzielenia i tak naprawdę nie traktują kodu i danych zbyt odmiennie. (LISP są najbardziej znanym przykładem, ale istnieje wiele innych języków, takich jak REBOL, które to robią)
Benjamin Gruenbaum

1
@BenjaminGruenbaum Mówiłem o skompilowanym kodzie w pamięci, a nie o poziomie językowym.
svick

C ma wskaźniki funkcji, które przynajmniej nieznacznie można uznać za kolejną wartość. Na pewno niebezpieczna wartość, z którą warto zadzierać, ale zdarzały się dziwniejsze rzeczy.
Patrick Hughes,

@PatrickHughes Tak, wspominam o nich w drugim punkcie. Ale wskaźniki funkcji są zupełnie różne od funkcji.
svick,

59

To dlatego, że ludzie powinni wiedzieć, że funkcje nie są po prostu „innym nazwanym bytem”. Czasami sensowne jest manipulowanie nimi jako takimi, ale nadal można je rozpoznać na pierwszy rzut oka.

Tak naprawdę nie ma znaczenia, co komputer myśli o składni, ponieważ niezrozumiała kropla znaków jest odpowiednia dla maszyny do interpretacji, ale byłoby to prawie niemożliwe do zrozumienia i utrzymania przez ludzi.

To naprawdę ten sam powód, dla którego mamy pętle while i for, switch i if else itp., Mimo że wszystkie z nich ostatecznie sprowadzają się do instrukcji porównywania i skakania. Powodem jest to, że jest to z korzyścią dla ludzi, którzy utrzymują i rozumieją kod.

Posiadanie funkcji jako „innego nazwanego bytu” tak, jak proponujesz, sprawi, że twój kod będzie trudniejszy do zobaczenia, a przez to trudniejszy do zrozumienia.


12
Nie zgadzam się z tym, że traktowanie funkcji jako nazwanych jednostek niekoniecznie utrudnia zrozumienie kodu. Prawie na pewno jest to prawdą w przypadku każdego kodu zgodnego z paradygmatem proceduralnym, ale może nie być prawdą w przypadku kodu zgodnego z paradygmatem funkcjonalnym.
Kyle Strand,

29
„To naprawdę ten sam powód, dla którego mamy do czynienia z pętlami while i for, switch i if else itp., Mimo że wszystkie z nich ostatecznie sprowadzają się do instrukcji porównania i skoku. ” +1 do tego. Gdyby wszyscy programiści znali język maszynowy, nie potrzebowalibyśmy tych wszystkich wymyślnych języków programowania (wysokiego lub niskiego poziomu). Ale prawda jest taka: każdy ma inny poziom komfortu, jeśli chodzi o to, jak blisko maszyny chcą pisać swój kod. Gdy znajdziesz swój poziom, musisz trzymać się podanej składni (lub napisać własną specyfikację języka i kompilator, jeśli naprawdę Ci przeszkadza).
Hoki,

12
+1. Bardziej zwięzłą wersją może być „Dlaczego wszystkie języki naturalne odróżniają rzeczowniki od czasowników?”
msw

2
„niezrozumiała kropla znaków jest w porządku dla maszyny do interpretacji, ale byłoby to prawie niemożliwe dla ludzi do zrozumienia i utrzymania” Co za pochopna kwalifikacja dla tak skromnej zmiany składniowej, jak zaproponowano w pytaniu. Szybko dostosowuje się do składni. Ta propozycja jest znacznie mniej radykalnym odejściem od konwencji niż metoda składni wywołania metody OO. Metoda (args) była w swoim czasie, ale ta ostatnia nie okazała się prawie niemożliwa do zrozumienia i utrzymania przez ludzi. Pomimo faktu, że w większości języków OO metody nie powinny być uważane za przechowywane w instancjach klas.
Marc van Leeuwen

4
@MarcvanLeeuwen. Twój komentarz jest prawdziwy i ma sens, ale pewne zamieszanie wywołuje sposób redagowania oryginalnego postu. Są właściwie 2 pytania: (i) Dlaczego tak jest? oraz (ii) Dlaczego nie w ten sposób? . Odpowiedzią whatsisnamebyło bardziej zajęcie się pierwszym punktem (i zaalarmowanie o pewnym niebezpieczeństwie usunięcia tych zabezpieczających przed awarią), podczas gdy twój komentarz jest bardziej związany z drugą częścią pytania. Rzeczywiście można zmienić tę składnię (i jak już opisałeś, robiono to już wiele razy ...), ale nie będzie to pasować do wszystkich (ponieważ oop też nie wszystkim
Hoki

10

Być może zechcesz dowiedzieć się, że w czasach prehistorycznych język o nazwie ALGOL 68 stosował składnię zbliżoną do tego, co proponujesz. Uznając, że identyfikatory funkcji są powiązane z wartościami, tak jak inne identyfikatory, można w tym języku zadeklarować funkcję (stałą) przy użyciu składni

nazwa typu funkcji = ( lista parametrów ) typ wyniku : treść ;

Konkretnie twój przykład przeczytałby

PROC (INT)INT add one = (INT n) INT: n+1;

Uznając redundancję, że typ początkowy można odczytać z RHS deklaracji, a ponieważ typ funkcji zawsze zaczyna się od PROC, można to (i zwykle byłoby) zawrzeć z

PROC add one = (INT n) INT: n+1;

ale pamiętaj, że =nadal występuje przed listą parametrów. Należy również zauważyć, że jeśli chcesz zmiennej funkcji (do której można później przypisać inną wartość tego samego typu funkcji), =należy ją zastąpić :=, podając jedną z

PROC (INT)INT func var := (INT n) INT: n+1;
PROC func var := (INT n) INT: n+1;

Jednak w tym przypadku obie formy są w rzeczywistości skrótami; ponieważ identyfikator func varoznacza odwołanie do funkcji generowanej lokalnie, forma w pełni rozwinięta byłaby

REF PROC (INT)INT func var = LOC PROC (INT)INT := (INT n) INT: n+1;

Ta szczególna forma składniowa jest łatwa do przyzwyczajenia, ale najwyraźniej nie cieszyła się dużą popularnością w innych językach programowania. Nawet funkcjonalnych języków programowania takich jak Haskell preferują styl f n = n+1z = następujących listę parametrów. Myślę, że przyczyna jest głównie psychologiczna; w końcu nawet matematycy często nie preferują, tak jak ja, f = nn + 1 nad f ( n ) = n + 1.

Nawiasem mówiąc, powyższa dyskusja podkreśla jedną ważną różnicę między zmiennymi a funkcjami: definicje funkcji zwykle wiążą nazwę z jedną określoną wartością funkcji, której nie można później zmienić, podczas gdy definicje zmiennych zwykle wprowadzają identyfikator o wartości początkowej , ale taki, który może się zmienić później. (Nie jest to reguła bezwzględna; zmienne funkcyjne i stałe niefunkcjonalne występują w większości języków.) Ponadto w językach kompilowanych wartość związana w definicji funkcji jest zwykle stałą czasową kompilacji, więc wywołania funkcji mogą być skompilowany przy użyciu stałego adresu w kodzie. W C / C ++ jest to nawet wymóg; odpowiednik ALGOL 68

PROC (REAL) REAL f = IF mood=sunny THEN sin ELSE cos FI;

nie można napisać w C ++ bez wprowadzenia wskaźnika funkcji. Tego rodzaju specyficzne ograniczenia uzasadniają użycie innej składni dla definicji funkcji. Jednak zależą one od semantyki języka, a uzasadnienie nie dotyczy wszystkich języków.


1
„Matematycy nie preferują f = nn + 1 nad f ( n ) = n + 1” ... Nie wspominając o fizykach, którzy lubią pisać tylko f = n + 1 ...
lewo około

1
@leftaroundabout, ponieważ ⟼ jest trudny do napisania. Szczerze.
Pierre Arlaud

8

Jako przykłady podałeś Java i Scalę. Przeoczyłeś jednak ważny fakt: to nie są funkcje, tylko metody. Metody i funkcje są zasadniczo różne. Funkcje są obiektami, metody należą do obiektów.

W Scali, która ma zarówno funkcje, jak i metody, istnieją następujące różnice między metodami i funkcjami:

  • metody mogą być ogólne, funkcje nie
  • metody mogą mieć nie, jedną lub wiele list parametrów, funkcje zawsze mają dokładnie jedną listę parametrów
  • metody mogą mieć niejawną listę parametrów, funkcje nie
  • metody mogą mieć opcjonalne parametry z domyślnymi argumentami, funkcje nie mogą
  • metody mogą mieć powtarzane parametry, funkcje nie
  • metody mogą mieć parametry według nazw, funkcje nie
  • metody mogą być wywoływane z nazwanymi argumentami, funkcje nie
  • funkcje są obiektami, metody nie

Tak więc proponowana zamiana po prostu nie działa, przynajmniej w tych przypadkach.


2
„Funkcje są obiektami, metody należą do obiektów”. Czy to powinno dotyczyć wszystkich języków (takich jak C ++)?
svick

9
„metody mogą mieć parametry według nazwy, funkcje nie mogą” - to nie jest podstawowa różnica między metodami a funkcjami, to tylko dziwactwo Scali. To samo z większością (wszystkich?) Pozostałych.
user253751

1
Metody są zasadniczo tylko specjalnym rodzajem funkcji. -1. Zauważ, że Java (i przez rozszerzenie Scala) nie ma innych funkcji. To „funkcje” są obiektami z jedną metodą (na ogół funkcje nie mogą być obiektami ani nawet wartościami pierwszej klasy; czy są one zależne od języka).
Jan Hudec

@JanHudec: Semantycznie Java ma zarówno funkcje, jak i metody; w odniesieniu do funkcji używa terminologii „metody statyczne”, ale taka terminologia nie usuwa rozróżnienia semantycznego.
supercat

3

Powody, o których mogę myśleć to:

  • Kompilatorowi łatwiej jest wiedzieć, co deklarujemy.
  • Ważne jest, abyśmy (w trywialny sposób) wiedzieli, czy jest to funkcja, czy zmienna. Funkcje są zwykle czarnymi skrzynkami i nie dbamy o ich wewnętrzne wdrożenie. Nie podoba mi się wnioskowanie o typach zwracanych w Scali, ponieważ uważam, że łatwiej jest używać funkcji, która ma typ zwracany: często jest to jedyna dostarczona dokumentacja.
  • Najważniejszą z nich jest przestrzeganie strategii tłumu , która jest używana przy projektowaniu języków programowania. C ++ został stworzony do kradzieży programistów C, a Java została zaprojektowana w sposób, który nie przeraża programistów C ++, a C # w celu przyciągnięcia programistów Java. Nawet C #, który moim zdaniem jest bardzo nowoczesnym językiem z niesamowitym zespołem za nim, skopiował kilka błędów z Javy lub nawet z C.

2
„… I C # w celu przyciągnięcia programistów Java” - jest to wątpliwe, C # jest znacznie bardziej podobny do C ++ niż Java. a czasem wygląda na to, że celowo został niezgodny z Javą (bez symboli wieloznacznych, bez wewnętrznych klas, bez kowariancji typu zwracanego…)
Sarge Borsch

1
@SargeBorsch Nie sądzę, aby celowo został niezgodny z Javą, jestem prawie pewien, że zespół C # po prostu starał się to zrobić dobrze lub zrobić to lepiej, bez względu na to, jak chcesz na to spojrzeć. Microsoft już napisał własną Javę i JVM i został pozwany. Więc zespół C # był prawdopodobnie bardzo zmotywowany do zaćmienia Javy. Jest to z pewnością niekompatybilne i lepiej dla niego. Java zaczęła od kilku podstawowych ograniczeń i cieszę się, że zespół C # postanowił na przykład zrobić generyczne inaczej (widoczne w systemie pisma, a nie tylko w cukrze).
codenheim

2

Odwracając pytanie, jeśli ktoś nie jest zainteresowany edycją kodu źródłowego na maszynie, która jest wyjątkowo ograniczona RAM-em, lub zminimalizować czas na odczytanie go z dyskietki, co jest złego w użyciu słów kluczowych?

Z pewnością lepiej jest czytać x=y+zniż store the value of y plus z into x, ale to nie znaczy, że znaki interpunkcyjne są z natury „lepsze” niż słowa kluczowe. Jeśli zmienne i, ji kIntegeri xReal, rozważ następujące wiersze w Pascal:

k := i div j;
x := i/j;

Pierwsza linia wykona obcinający podział na liczby całkowite, podczas gdy druga przeprowadzi podział na liczby rzeczywiste. Rozróżnienia można dokonać ładnie, ponieważ Pascal używa divjako operatora dzielenia liczb całkowitych, zamiast próbować używać znaku interpunkcyjnego, który ma już inny cel (dzielenie liczb rzeczywistych).

Chociaż istnieje kilka kontekstów, w których pomocne może być zwięzłe zdefiniowanie funkcji (np. Lambda, która jest używana jako część innego wyrażenia), funkcje na ogół powinny się wyróżniać i być łatwo rozpoznawalne wizualnie jako funkcje. O ile rozróżnienie może być znacznie bardziej subtelne i można używać wyłącznie znaków interpunkcyjnych, jaki byłby sens? Powiedzenie Function Foo(A,B: Integer; C: Real): Stringwyjaśnia, jak nazywa się funkcja, jakich parametrów oczekuje i co zwraca. Może można by go skrócić o sześć lub siedem znaków, zastępując Functionje niektórymi znakami interpunkcyjnymi, ale co by się zyskało?

Inną rzeczą, na którą należy zwrócić uwagę, jest to, że istnieje najbardziej podstawowa różnica między deklaracją, która zawsze kojarzy nazwę z określoną metodą lub konkretnym powiązaniem wirtualnym, a taką, która tworzy zmienną, która początkowo identyfikuje określoną metodę lub wiązanie, ale można zmienić w czasie wykonywania, aby zidentyfikować inną. Ponieważ są to bardzo semantycznie bardzo różne pojęcia w większości struktur proceduralnych, sensowne jest, aby miały one inną składnię.


3
Nie sądzę, że to pytanie dotyczy zwięzłości. Zwłaszcza, że ​​na przykład void f() {}jest w rzeczywistości krótszy niż równoważnik lambda w C ++ ( auto f = [](){};), C # ( Action f = () => {};) i Java ( Runnable f = () -> {};). Zwięzłość lambd wynika z wnioskowania typu i pomijania return, ale nie sądzę, że ma to związek z tym, o co pytają te pytania.
svick,

2

Przyczyną może być to, że te języki nie są wystarczająco funkcjonalne, że tak powiem. Innymi słowy, rzadko definiujesz funkcje. Dlatego użycie dodatkowego słowa kluczowego jest dopuszczalne.

W językach ML lub Miranda, OTOH, definiujesz funkcje przez większość czasu. Spójrz na przykład na kod Haskell. Jest to dosłownie głównie sekwencja definicji funkcji, wiele z nich ma funkcje lokalne i funkcje lokalne tych funkcji lokalnych. Dlatego zabawne słowo kluczowe w Haskell byłoby błędem tak wielkim, jak wymaganie instrukcji assingment w imperatywnym języku, aby rozpocząć od przypisania . Przyczyna przypisania jest prawdopodobnie zdecydowanie najczęstszym stwierdzeniem.


1

Osobiście nie widzę fatalnej wady w twoim pomyśle; może się okazać, że trudniej jest wyrazić pewne rzeczy przy użyciu nowej składni i / lub może się okazać, że trzeba je poprawić (dodając różne specjalne przypadki i inne funkcje itp.), ale wątpię, byś się znalazł konieczność całkowitego porzucenia pomysłu.

Proponowana składnia przypomina mniej więcej wariant niektórych stylów notacji używanych czasami do wyrażania funkcji lub typów funkcji w matematyce. Oznacza to, że podobnie jak wszystkie gramatyki, prawdopodobnie bardziej spodoba się niektórym programistom niż innym. (Jako matematyk lubię to.)

Należy jednak pamiętać, że w większości języków The def-Style Składnia (czyli tradycyjna składnia) nie zachowują się inaczej od standardowego przypisania zmiennej.

  • W Ci C++rodziny, funkcje nie są na ogół traktowane jako „obiekty”, czyli kawałki wpisywanych danych do skopiowania i umieść na stosie i etażerka. (Tak, możesz mieć wskaźniki funkcji, ale te nadal wskazują na kod wykonywalny, a nie na „dane” w typowym znaczeniu).
  • W większości języków OO istnieje specjalna obsługa metod (tj. Funkcji składowych); oznacza to, że nie są to tylko funkcje zadeklarowane w zakresie definicji klasy. Najważniejszą różnicą jest to, że obiekt, na którym wywoływana jest metoda, jest zwykle przekazywany jako domyślny pierwszy parametr do metody. Python wyraźnie to selfokreśla (który, nawiasem mówiąc, tak naprawdę nie jest słowem kluczowym; możesz uczynić dowolny poprawny identyfikator pierwszym argumentem metody).

Musisz rozważyć, czy twoja nowa składnia dokładnie (i miejmy nadzieję intuicyjnie) reprezentuje to, co faktycznie robi kompilator lub interpreter. Może pomóc odczytać, powiedzmy, różnicę między lambdami a metodami w Rubim; da ci to wyobrażenie o tym, jak twój paradygmat funkcji to tylko dane różni się od typowego paradygmatu OO / procedury.


1

Dla niektórych języków funkcje nie są wartościami. W takim języku można to powiedzieć

val f = << i : int  ... >> ;

jest definicją funkcji, natomiast

val a = 1 ;

deklaruje stałą, jest myląca, ponieważ używasz jednej składni w znaczeniu dwóch rzeczy.

Inne języki, takie jak ML, Haskell i Scheme, traktują funkcje jako wartości 1. klasy, ale zapewniają użytkownikowi specjalną składnię do deklarowania stałych wartości funkcji. * Stosują zasadę, że „użycie skraca formę”. Tzn. Jeśli konstrukcja jest zarówno powszechna, jak i pełna, powinieneś dać użytkownikowi skrót. Podawanie użytkownikowi dwóch różnych składni, które oznaczają dokładnie to samo, jest nieeleganckie; czasem elegancję należy poświęcić na rzecz użyteczności.

Jeśli w twoim języku funkcje są pierwszej klasy, to dlaczego nie spróbować znaleźć składni, która jest na tyle zwięzła, że ​​nie będziesz miał ochoty znaleźć cukru syntaktycznego?

-- Edytować --

Kolejną kwestią, o której nikt jeszcze nie wspomniał, jest rekurencja. Jeśli pozwolisz

{ 
    val f = << i : int ... g(i-1) ... >> ;
    val g = << j : int ... f(i-1) ... >> ;
    f(x)
}

i pozwalasz

{
    val a = 42 ;
    val b = a + 1 ;
    a
} ,

czy wynika z tego, że powinieneś zezwolić

{
    val a = b + 1 ; 
    val b = a - 1 ;
    a
} ?

W leniwym języku (jak Haskell) nie ma tutaj problemu. W języku zasadniczo pozbawionym kontroli statycznych (jak LISP) nie ma tutaj problemu. Ale w chętnie sprawdzanym statycznie języku musisz uważać na to, jak zdefiniowane są reguły sprawdzania statycznego, jeśli chcesz zezwolić na pierwsze dwa, a zabronić na ostatnie.

- Koniec edycji -

* Można argumentować, że Haskell nie należy do tej listy. Zapewnia dwa sposoby deklarowania funkcji, ale oba są w pewnym sensie uogólnieniem składni do deklarowania stałych innych typów


0

Może to być przydatne w dynamicznych językach, w których typ nie jest tak ważny, ale nie jest tak czytelny w statycznych językach, w których zawsze chcesz znać typ swojej zmiennej. Ponadto w językach obiektowych bardzo ważna jest znajomość typu zmiennej, aby wiedzieć, jakie operacje obsługuje.

W twoim przypadku funkcja z 4 zmiennymi to:

(int, long, double, String => int) addOne = (x, y, z, s) => {
  return x + 1;
}

Kiedy patrzę na nagłówek funkcji i widzę (x, y, z, s), ale nie znam typów tych zmiennych. Jeśli chcę wiedzieć, który typ zjest trzecim parametrem, muszę spojrzeć na początek funkcji i zacząć liczyć 1, 2, 3, a następnie zobaczyć, czy typ jest double. W ten pierwszy sposób patrzę bezpośrednio i widzę double z.


3
Oczywiście istnieje cudowna rzecz zwana wnioskowaniem typu, która pozwala napisać coś var addOne = (int x, long y, double z, String s) => { x + 1 }w wybranym przez użytkownika niemononicznym, statycznie typowanym języku (przykłady: C #, C ++, Scala). Nawet bardzo ograniczone wnioskowanie typu lokalnego używane przez C # jest całkowicie wystarczające do tego. Tak więc ta odpowiedź krytykuje jedynie konkretną składnię, która jest wątpliwa i nie jest nigdzie używana (chociaż w składni Haskella występuje bardzo podobny problem).
amon

4
@amon Jego odpowiedź może krytykować składnię, ale wskazuje również, że zamierzona składnia w pytaniu może nie być „naturalna” dla wszystkich.

1
@amon Przydatność wnioskowania typu nie jest dla wszystkich cudowną rzeczą. Jeśli częściej czytasz kod niż podczas pisania kodu, wnioskowanie o typie jest złe, ponieważ podczas czytania kodu musisz wnioskować o typie; i jest znacznie szybszy do odczytania typu niż zastanowienia się nad tym, który typ jest używany.
m3th0dman

1
Krytyka wydaje mi się słuszna. W przypadku Javy OP wydaje się myśleć, że [wejście, wyjście, nazwa, wejście] jest prostszą / bardziej przejrzystą funkcją def niż po prostu [wyjście, nazwa, wejście]? Jak stwierdza @ m3th0dman, przy dłuższych podpisach podejście „czystsze” PO staje się bardzo rozwlekłe.
Michael

0

Istnieje bardzo prosty powód, aby wprowadzić takie rozróżnienie w większości języków: istnieje potrzeba rozróżnienia oceny i deklaracji . Twój przykład jest dobry: dlaczego nie lubisz zmiennych? Cóż, wyrażenia zmiennych są natychmiast oceniane.

Haskell ma specjalny model, w którym nie ma rozróżnienia między oceną a deklaracją, dlatego nie ma potrzeby stosowania specjalnego słowa kluczowego.


2
Ale pewna składnia funkcji lambda również to rozwiązuje, więc dlaczego tego nie użyć?
svick,

0

Funkcje są zadeklarowane inaczej niż literały, obiekty itp. W większości języków, ponieważ są używane inaczej, debugowane inaczej i stwarzają różne potencjalne źródła błędów.

Jeśli odwołanie do obiektu dynamicznego lub obiektu zmiennego zostanie przekazane do funkcji, funkcja może zmieniać wartość obiektu podczas działania. Tego rodzaju efekt uboczny może utrudnić śledzenie tego, co zrobi funkcja, jeśli zostanie zagnieżdżona w złożonym wyrażeniu, i jest to powszechny problem w językach takich jak C ++ i Java.

Rozważ debugowanie jakiegoś modułu jądra w Javie, gdzie każdy obiekt ma operację toString (). Chociaż można oczekiwać, że metoda toString () powinna przywrócić obiekt, może być konieczne zdemontowanie i ponowne złożenie obiektu w celu przekształcenia jego wartości na obiekt String. Jeśli próbujesz debugować metody, które toString () będzie wywoływać (w scenariuszu z zaczepieniem i szablonem), aby wykonać swoją pracę i przypadkowo podświetlić obiekt w oknie zmiennych większości IDE, może to spowodować awarię debugera. Wynika to z faktu, że IDE spróbuje toString () obiektu, który wywołuje kod, który właśnie debugujesz. Żadna prymitywna wartość nigdy nie jest taka bzdura, ponieważ semantyczne znaczenie prymitywnych wartości jest definiowane przez język, a nie programistę.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.