Czy składnia języków programowania zależy od ich implementacji?

12

Chociaż moje pytanie może być zupełnie nieistotne, ale wyczułem wzorzec między większością języków programowania a ich oficjalnymi implementacjami.

Języki interpretowane ( interpretowane bajtowo?), Takie jak Python, Lua itp., Zazwyczaj mają wyjątkowo łagodną i łatwą składnię i generalnie nie zawierają żadnych typów lub nie wymagają od programisty jawnego pisania typów zmiennych w kodzie źródłowym;

Skompilowane języki, takie jak C, C ++, Pascal itp., Zwykle mają ścisłą składnię, generalnie mają typy i przeważnie wymagają więcej kodu / czasu programowania

Języki, których oficjalne implementacje są skompilowane w JIT, takie jak Java / C #, zazwyczaj stanowią unikalny kompromis między powyższymi dwoma, a niektóre z nich mają najlepsze cechy.

Niektóre z bardziej nowoczesnych skompilowanych języków programowania, takie jak D i Vala (oraz implementacja Java GNU GJC) są być może wyjątkiem od tej reguły i przypominają składnię oraz funkcje języków skompilowanych w JIT, takich jak Java i C #.

Moje pierwsze pytanie brzmi: czy to jest naprawdę istotne? Czy to tylko zbieg okoliczności, że większość interpretowanych języków ma łatwą składnię, te skompilowane w JIT mają umiarkowaną składnię i funkcje itp.

Po drugie, jeśli to nie przypadek, to dlaczego tak jest? Na przykład, czy niektóre funkcje można zaimplementować tylko w języku programowania, jeśli, powiedzmy, kompilujesz JIT?

ApprenticeHacker
źródło
@YannisRizos przepraszam, to nie jest cytat. Chciałem tylko to podkreślić. Zmienię to.
ApprenticeHacker
1
Fajnie, myślałem, że to nie był cytat, ale może prowadzić do tego, że odpowiadający myślą, że to był jeden, i nie próbują go obalić (lub ślepo się z tym zgadzają) ... Zauważyłem podobne wzorce, ale niestety nie mam dobrych odpowiedź.
yannis
@ R.MartinhoFernandes przepraszam, nie wiedziałem o tym. Zmienię to (ponownie).
ApprenticeHacker
4
Perl jest dynamicznie typowany dla typów zdefiniowanych przez użytkownika, typowany statycznie w odniesieniu do rozróżniania tablic, skrótów, skalarów i podprogramów, i silnie typowany poprzez użycie ścisłe, interpretowane i kompilowane JIT (nie w tym samym czasie) ... Ilekroć ktoś próbuje aby nadać sens projektowaniu języka, wrzucanie Perla jest zawsze zabawne ...
yannis
3
Co rozumiesz przez „łagodną składnię” vs. „ścisłą składnię”? Wszystkie są językami formalnymi i żaden nie uruchomi kodu źródłowego z błędami składniowymi.
nikie

Odpowiedzi:

17

Nie ma żadnego związku między semantyką i składnią. Języki skompilowane w Homoiconic, takie jak Scheme, mają dość minimalistyczną składnię. Meta-języki skompilowane na niskim poziomie, takie jak Forth, są jeszcze prostsze. Niektóre ściśle skompilowane języki kompilowane są oparte na trywialnej składni (think ML, Haskell). OTOH, składnia Pythona jest bardzo ciężka pod względem szeregu reguł składniowych.

I tak, pisanie nie ma nic wspólnego ze składnią, jest po stronie semantyki języka, chyba że jest to coś tak wypaczonego jak C ++, w którym nie można nawet parsować bez posiadania wszystkich dostępnych informacji.

Ogólną tendencją jest to, że języki, które ewoluowały zbyt długo i nie zawierały żadnych zabezpieczeń projektowych przed odchyleniami składni, prędzej czy później przekształciłyby się w obrzydliwości składniowe.

Logika SK
źródło
+1 za zmuszanie mnie do szukania „homoiconic” ... I za subtelny ukłon w stronę PHP ...
yannis
1
+1, języki, które ewoluowały zbyt długo i nie zawierały żadnych zabezpieczeń projektowych , czy dotyczy to również Delphi / Object-Pascal?
ApprenticeHacker
1
@ThomasEding, mylisz się. Ta sama semantyka może być zaimplementowana w połączeniu z bardzo szeroką gamą stylów składni, nawet w języku bez składni (takim jak Lisp lub Forth). Tej samej składni można używać z bardzo szeroką gamą różnych semantyków - np. Składnia wyrażeń C i Verilog jest prawie taka sama, ale semantyka jest zupełnie inna.
SK-logic,
1
@ SK-logic - Tylko dlatego, że jest złożony i Turing-kompletny, nie oznacza, że ​​nie jest to przynajmniej bardzo duża część składni programu. Parsowanie różnych języków jest kompletne w Turinga, co nie magicznie czyni parsowania czymś, co „nie ma nic wspólnego ze składnią”. Składnia nie dotyczy „zakresu”, dotyczy reguł dotyczących struktury instrukcji w języku - nie mówiąc nic o ich znaczeniu. Sprawdzanie typów i wnioskowanie o typach działają na drzewach składniowych programów bez ich wykonywania - decydują o strukturze programu, nie mówiąc nic o ...
Jack
1
@Jack, próbujesz na nowo zdefiniować składnię. Nie ma żadnych praktycznych języków, które wymagałyby pełnego parsera Turinga, większość jest niczym innym jak kontekstowym. I tu powinna pozostać składnia. Nie rozszerzaj tego (już zbyt rozciągniętego) pojęcia nigdzie indziej. Wspomniałem już o izomorfizmie Curry'ego-Howarda - chodzi o semantykę, daleko poza zwykłymi zasadami poprawności. Myślę, że sam termin „ type checking” jest wyjątkowo przeciwny do zamierzonego i nie powinien być używany, jest bardzo mylący, nie odzwierciedla charakteru systemów typu.
SK-logic
6

Przeważnie jest to zbieg okoliczności.

Języki programowania ewoluowały z biegiem czasu, a technologia kompilatorów i tłumaczy poprawiła się. Wydajność bazowego przetwarzania (tj. Czas kompilacji, koszty tłumaczenia, czas wykonania itp.) Jest również mniej ważna, ponieważ główne platformy obliczeniowe wzrosły.

Składnia języka nie mają wpływu - na przykład Pascal został bardzo starannie zaprojektowany tak, to może użyć pojedynczego przejścia kompilator - czyli jeden podaniem źródła i masz plikiem wykonywalnym kodu maszynowego. Z drugiej strony Ada nie zwracała na to uwagi, a kompilatory Ada są niezwykle trudne do napisania - większość wymaga więcej niż jednego przejścia. (Jednym z bardzo dobrych kompilatorów Ady, z których korzystałem wiele lat temu, był kompilator 8-przebiegowy. Jak można się domyślić, był bardzo powolny).

Jeśli spojrzysz na stare języki, takie jak Fortran (skompilowany) i BASIC (zinterpretowany lub skompilowany), mają one / miały bardzo ścisłe reguły składniowe i semantyczne. [W przypadku BASIC, to nie jest stary BASIC Bills, musisz wrócić wcześniej do oryginału.]

Z drugiej strony, patrząc na inne starsze rzeczy, takie jak APL (świetna zabawa), to było coś w rodzaju dynamicznego pisania. Został również ogólnie zinterpretowany, ale można go również skompilować.

Łagodna składnia jest trudna - jeśli oznacza to, że masz rzeczy opcjonalne lub można je wywnioskować, oznacza to, że język ma wystarczające bogactwo, aby można go było ubić. Z drugiej strony, BASIC miał to wiele lat temu, kiedy zdanie „LET” stało się opcjonalne!

Wiele pomysłów, które teraz widzisz (na przykład pisanie bez czcionek lub dynamiczne) jest w rzeczywistości bardzo starych - pojawiło się po raz pierwszy w latach siedemdziesiątych lub wczesnych osiemdziesiątych. Sposób ich użycia oraz języki, w których są używane te pomysły, zmieniły się i wzrosły. Zasadniczo jednak większość nowości to tak naprawdę stare rzeczy przebrane w nowe ubrania.

Oto kilka przykładów z mojej głowy:

  • APL: pisanie dynamiczne. Ogólnie interpretowane. Pochodzi z lat 60. / 70.
  • PODSTAWOWY: mocne lub dynamiczne pisanie. Zinterpretowany lub skompilowany. Lata 70. i wiele innych.
  • Fortran: mocne pisanie. Opracowano Lata 60. lub wcześniejsze.
  • Algol68: mocne pisanie. Opracowano Lata 60.
  • PL / 1: mocne pisanie. Opracowano Lata 60.
  • Pascal: mocne pisanie. Opracowano Lata 70. (Ale w latach 80. były kompilatory P-System bardzo podobne do kompilatorów JIT!)
  • Niektóre implementacje Fortrana i inne przez DEC we wczesnych dniach zostały częściowo skompilowane, a częściowo zinterpretowane.
  • Smalltalk: dynamiczne pisanie. Skompilowany do kodu bajtowego, który jest interpretowany. Lata 80.
  • Prolog: więcej dziwności. Funkcjonalny. Skompilowany (Turbo Prolog, ktoś?). Lata 80.
  • C: mocne pisanie (ha ha). Opracowano Lata 60. .. dziś.
  • Ada: mocne pisanie. Opracowano Lata 80.
  • Perl: pisanie dynamiczne. (Silna składnia). Interpretowane. Lata 90. (?).

Mógłbym kontynuować.

  • Narożnik Nitpickers: Wiele interpretowanych języków jest tokenizowanych lub „kompilowanych bajtowo” w momencie, gdy źródło jest ładowane / wczytywane. To sprawia, że ​​kolejne operacje interpretera są znacznie prostsze. Czasami możesz zapisać skompilowaną bajtowo wersję kodu. Czasami nie możesz. Nadal jest interpretowane.

Aktualizacja: Ponieważ nie byłem wystarczająco jasny.

Pisanie może się znacznie różnić.

Typowe statyczne pisanie w czasie kompilacji jest powszechne (np. C, Ada, C ++, Fortan itp.). Tutaj deklarujesz RZECZ TYPU i tak jest na zawsze.

Możliwe jest także dynamiczne pisanie, w którym rzecz wybiera typ, który jest do niej przypisany. Na przykład PHP i niektóre wczesne BASIC oraz APL, w których przypisujesz liczbę całkowitą do zmiennej i od tego czasu jest to liczba całkowita. Jeśli później przypisałeś mu ciąg, to był to typ ciągu. I tak dalej.

A potem jest luźne pisanie, na przykład PHP, w którym można robić naprawdę dziwaczne rzeczy, takie jak przypisać liczbową liczbę całkowitą (cytowaną, więc jest to ciąg znaków) do zmiennej, a następnie dodać do niej liczbę. (np. „5” + 5 spowoduje 10). To kraina dziwacznych, ale czasami bardzo, bardzo przydatnych.

JEDNAK są to funkcje zaprojektowane w języku. Wdrożenie właśnie to sprawia.

szybko
źródło
13
Silne pisanie nie jest odpowiednikiem pisania dynamicznego. Jest to odpowiednik słabego pisania. Odpowiednikiem typowania dynamicznego jest typowanie statyczne: w jednym typ wyrażeń w programie można poznać statycznie (tzn. Bez uruchamiania programu); w innym typy można znać tylko dynamicznie (tzn. program musi zostać uruchomiony).
R. Martinho Fernandes,
Tak i oba warianty BASIC i APL robiły to pod koniec lat siedemdziesiątych. Typy APL nie są do końca takie, jak je dziś rozumiemy (takie jak liczby całkowite / zmiennoprzecinkowe o typie uniwersalnym, ale mogą to być również wektory, łańcuchy i macierze wielowymiarowe).
szybko_niedz.
Tłumacz Fortrana jest nadal szeroko stosowany (patrz Cernlib i PAW). A jego potomek, ROOT, jest zbudowany na interprecie C ++.
SK-logic,
Szczerze mówiąc, nie jestem do końca jasne, jak silne / słabe i statyczne / dynamiczne pisanie odnosi się do składni. Ale jakość odpowiedzi była całkiem dobra, więc po prostu unikam głosowania. Pisałbym w klasie C jako „statyczny / słaby” (patrzenie na przechowywaną wartość tak, jakby to był inny typ, było trywialne, być może pomylono tę wartość).
Vatine
@Vatine - powiedziałbym, że silny w czasie kompilacji, nieistniejący w czasie wykonywania - jeśli chcesz tego w ten sposób. Możesz to zrobić za pomocą wskaźników i ich odpowiedników w wielu językach. Jest to nawet możliwe w klasycznym pascalu przy użyciu zapisów wariantowych, aw Adzie przy użyciu UNCHECKED_CONVERSION (choć trudne, jest to możliwe).
szybko_niedz.
2

Myślę, że jest na odwrót: implementacja zależy od składni. Na przykład jeśli twoja składnia pozwala na refleksję, implementacja musi zapewnić środowisko wykonawcze, które to obsługuje.

StackedCrooked
źródło
@IntermediateHacker: ale jest w Javie, więc powinienem być niesamowity
patrz
2

Ogólnie zgadzam się z szybko_nieś, że twoja obserwacja jest głównie wynikiem historii. To powiedziawszy, podstawowe rozumowanie sprowadza się do czegoś takiego:

The more modern a language is, the more comfortable it should be to use.

(Naprawdę nie jest to cytat, tylko moje własne sformułowanie.) Kiedy piszę comfortabletutaj, odnoszę się do tego, co nazwałeś best features of both. Mówiąc dokładniej, nie chcę wypowiadać się za lub przeciw pisaniu statycznemu / dynamicznemu lub ścisłej / łagodnej składni. Zamiast tego ważne jest, aby skupić się na programistach i zwiększyć ich poziom komfortu podczas pracy z językiem.

Oto kilka powodów, które nie zostały wymienione w poprzednich odpowiedziach, które mogą dostarczyć ci pomysłów na to, dlaczego obserwujesz te rzeczy (i wszystkie są oparte na historii programowania programowania języków):

  • Obecnie mamy setki języków programowania. Kiedy pojawia się nowy, jak może znaleźć szerokie grono odbiorców? To jest główny powód, dla którego nowe języki zawsze starają się podnieść poziom komfortu programistów. Jeśli język może zrobić to samo co starszy, ale może to zrobić znacznie łatwiej / prościej / bardziej elegancko / etc. możesz rozważyć zmianę.

  • Krzywa uczenia się idzie w parze z tym. W przeszłości mieliśmy niewiele języków i poświęcenie czasu na naukę jednego było tego warte. Nawet jeśli oznaczało to zainwestowanie dużo czasu. Ponownie zwiększa się komfort, jeśli wpadniesz na język, którego programiści mogą się bardzo szybko nauczyć. Jakakolwiek złożoność (np. Skomplikowana składnia) jest szkodliwa, a zatem jest coraz bardziej zmniejszana w nowszych językach.

  • Postęp technologiczny (tutaj bezpośredni historyczny powód) jest odpowiedzialny za to, że twórcy kompilatorów mogą teraz kłaść większy nacisk na komfort programisty. Na początku cieszyliśmy się, że mogliśmy w ogóle zbudować kompilator. Często wiązało się to jednak z poważnymi ograniczeniami. W miarę wzrostu technologicznego know-how byliśmy w stanie ponownie znieść te ograniczenia.

Ogólnie rzecz biorąc, w językach programowania i kompilatorach zaobserwowano rozwój podobny do typowych aplikacji dla użytkowników końcowych:

  1. Etap początkowy: fajnie jest mieć, ale najnowocześniejsza technologia zaledwie sprawia, że ​​działa kosztem wygody / użyteczności / czego nie.
  2. Ulepszenie technologiczne: Możemy budować te rzeczy solidniej, szybciej i łatwiej.
  3. Fokus zwraca się do użytkownika: podobnie jak ruch Web2.0 skupiający się na doświadczeniu użytkownika, nowe języki programowania koncentrują się na perspektywie programisty.
Szczery
źródło
(Not a quote really, just my own formulation.)Cóż, sformatowałeś go jako kod, a nie jako cytat, więc nie sądzę, żeby ktokolwiek myślał, że to cytat :)
yannis
3
Komfort wyraźnie zależy od smaku (który zawsze jest całkowicie subiektywny). Język, który najbardziej mi odpowiada, został opracowany w 1959 roku i nie mogę znieść niektórych języków, które pojawiły się w tym stuleciu.
SK-logic
1
Komfort zależy również od celu. Uruchamianie PHP lub Prologa na wbudowanym mikroprocesorze 8k dla kontrolera pralki może być „wygodne” do zaprogramowania, ale także cholernie trudne, aby dopasować go i działać z akceptowalną wydajnością.
szybko_now
0

Dany język programowania może, ale nie musi, ujawniać lub ograniczać wystarczającą ilość informacji semantycznych dla kompilatora, aby wydedukować, jak zredukować go do kodu wykonywalnego bez dodawania decyzji w czasie wykonywania („jaki typ jest ta zmienna?” Itp.). Niektóre języki są jawnie zaprojektowane do tworzenia ograniczenie to jest obowiązkowe lub łatwe do ustalenia.

Gdy kompilatory stają się mądrzejsze, mogą być w stanie odgadnąć lub sprofilować wystarczającą ilość informacji, aby wygenerować kod wykonywalny dla najbardziej prawdopodobnych ścieżek, nawet dla języków, które nie zostały wyraźnie zaprojektowane w taki sposób, aby ujawniać lub ograniczać te decyzje.

Jednak języki, w których kod (evalString ()) może zostać utworzony lub wprowadzony w czasie wykonywania (i inne rzeczy, których kompilator nie może wydedukować ani odgadnąć) mogą wymagać dostępności interpretera lub kompilatora JIT w czasie wykonywania, nawet przy próbach wstępnego skompiluj je.

W przeszłości język programowania i jego implementacja mogły ewoluować tak, aby pasowały do ​​pewnych ograniczeń sprzętowych, takich jak to, czy interpreter może zmieścić się w 4k lub 16k, lub czy kompilator może zakończyć się w mniej niż minutę czasu procesora. Gdy maszyny stają się coraz szybsze, stało się możliwe (ponowne) skompilowanie niektórych wcześniej interpretowanych programów tak szybko, jak programiści mogą nacisnąć klawisz powrotu lub interpretować kod źródłowy wcześniej skompilowanego programu szybciej niż nieco starszy sprzęt mógł uruchomić zoptymalizowane skompilowane pliki wykonywalne.

hotpaw2
źródło