Limbaj de programare

Un limbaj de programare  este un limbaj formal conceput pentru a scrie programe de calculator [1] [2] . Un limbaj de programare definește un set de reguli lexicale , sintactice și semantice care determină aspectul programului și acțiunile pe care executantul (de obicei un computer ) le va efectua sub controlul său.

De la crearea primelor mașini programabile, omenirea a venit cu peste opt mii de limbaje de programare (inclusiv ezoterice , vizuale și jucării ) [3] . În fiecare an numărul lor crește. Unele limbi sunt folosite doar de un număr mic de proprii dezvoltatori, altele devin cunoscute de milioane de oameni. Programatorii profesioniști pot fi pricepuți în mai multe limbaje de programare.

Un limbaj de programare este conceput pentru a scrie programe de calculator, care sunt un set de reguli care permit unui computer să efectueze un anumit proces de calcul , să organizeze gestionarea diferitelor obiecte etc. Un limbaj de programare diferă de limbajele naturale prin faptul că este proiectat pentru a controla un computer, în timp ce limbajele naturale sunt folosite în primul rând pentru comunicarea între oameni. Majoritatea limbajelor de programare folosesc constructe speciale pentru a defini și manipula structurile de date și pentru a controla procesul de calcul.

De regulă, un limbaj de programare este definit nu numai prin specificațiile standardului de limbaj , care îi definesc în mod formal sintaxa și semantica , ci și prin încarnările (implementarea) standardului  - instrumente software care asigură traducerea sau interpretarea programe în această limbă ; astfel de instrumente software diferă în funcție de producător, marcă și variantă (versiune), momentul lansării, caracterul complet al implementării standardului, caracteristicile suplimentare; poate avea anumite erori sau caracteristici de implementare care afectează practica de utilizare a limbajului sau chiar standardul acestuia.

Istorie

Stadiile incipiente ale dezvoltării

Putem spune că primele limbaje de programare au apărut chiar înainte de apariția computerelor electronice moderne: deja în secolul al XIX-lea au fost inventate dispozitive care pot fi numite programabile cu un anumit grad de convenție - de exemplu, o cutie muzicală (și mai târziu o pian mecanic ) folosind un cilindru metalic și un războaie de țesut Jacquard (1804) cu ajutorul cărților de carton. Pentru controlul acestora s-au folosit seturi de instrucțiuni care, în cadrul clasificării moderne, pot fi considerate prototipuri ale limbajelor de programare specifice domeniului . Semnificativ poate fi considerat „limbajul” în care Lady Ada Augusta (contesa de Lovelace) a scris în 1842 un program pentru calcularea numerelor Bernoulli pentru motorul analitic al lui Charles Babbage , care, dacă ar fi implementat, ar deveni primul computer din lume, deși mecanic - cu motor cu abur .

În 1930 - 1940, A. Church , A. Turing , A. Markov au dezvoltat abstracții matematice ( calculul lambda , mașina Turing , respectiv algoritmi normali ) - pentru a formaliza algoritmi .

În același timp, în anii 1940, au apărut calculatoarele digitale electrice și s-a dezvoltat un limbaj care poate fi considerat primul limbaj de programare pentru computer de nivel înalt - „ Plankalkül ”, creat de inginerul german K. Zuse în perioada 1943-1945 . [4] .

Programatorii de calculatoare de la începutul anilor 1950 , în special precum UNIVAC și IBM 701, foloseau direct codul mașinii la crearea programelor , înregistrarea programului pe care constă din unu și zero și care este considerat a fi limbajul de programare al primei generații (în timp ce diferite mașini de la diferiți producători au folosit coduri diferite, ceea ce necesita rescrierea programului la trecerea la alt computer).

Primul limbaj implementat practic a fost în 1949 așa-numitul „ Cod scurt ”, în care operațiile și variabilele erau codificate cu combinații de două caractere. A fost dezvoltat de Eckert–Mauchly Computer Corporation , care a produs UNIVAC, creat de unul dintre angajații lui Turing, John Mauchly . Mauchly și-a instruit personalul să dezvolte un traducător de formule matematice, dar pentru anii 1940 acest obiectiv era prea ambițios. Codul scurt a fost implementat folosind interpretul [5] .

Curând, această metodă de programare a fost înlocuită cu utilizarea limbajelor din a doua generație, limitate și de specificațiile unor mașini specifice , dar mai ușoară pentru uz uman datorită utilizării mnemotecilor (notație simbolică pentru instrucțiunile mașinii) și a capacității de a mapa numele la adresele din memoria mașinii. Ele sunt cunoscute în mod tradițional ca limbaje de asamblare și autocoduri . Cu toate acestea, atunci când se folosește un asamblator, a devenit necesară traducerea programului în cod de mașină înainte de a-l executa, pentru care au fost dezvoltate programe speciale, numite și asamblatori. Au existat, de asemenea, probleme cu portabilitatea unui program de la un computer de o arhitectură la alta și necesitatea ca un programator, atunci când rezolvă o problemă, să gândească în termeni de „nivel scăzut” - o celulă, o adresă, o comandă. . Mai târziu, limbile din a doua generație au fost îmbunătățite pentru a include suport pentru macrocomenzi .

De la mijlocul anilor 1950, au început să apară limbi din a treia generație, cum ar fi Fortran , Lisp și Cobol [6] . Limbajele de programare de acest tip sunt mai abstracte (se mai numesc și „limbaje de nivel înalt”) și universale, nu au o dependență rigidă de o anumită platformă hardware și de instrucțiunile mașinii utilizate pe aceasta. Un program într-un limbaj de nivel înalt poate fi executat (cel puțin în teorie, în practică există de obicei o serie de versiuni specifice sau dialecte ale implementării limbii) pe orice computer care are un traducător pentru această limbă (un instrument care traduce programul în limbajul mașinii, după care poate fi executat de procesor).

Versiunile actualizate ale acestor limbaje sunt încă în circulație în dezvoltarea de software și fiecare dintre ele a avut un anumit impact asupra dezvoltării ulterioare a limbajelor de programare [7] . Apoi, la sfârșitul anilor 1950, a apărut Algol , care a servit și ca bază pentru o serie de dezvoltări ulterioare în acest domeniu. Trebuie remarcat faptul că formatul și utilizarea limbajelor de programare timpurii au fost în mare măsură influențate de constrângerile interfeței [8] .

Îmbunătățire

În anii 1960 și 1970 s -au dezvoltat  principalele paradigme ale limbajelor de programare utilizate astăzi, deși în multe aspecte acest proces a fost doar o îmbunătățire a ideilor și conceptelor expuse în limbajele din prima a treia generație.

Fiecare dintre aceste limbaje a dat naștere unei familii de descendenți, iar majoritatea limbajelor de programare moderne se bazează în cele din urmă pe unul dintre ele.

În plus, în anii 1960 și 1970, a existat o dezbatere activă cu privire la necesitatea de a sprijini programarea structurată în anumite limbaje [14] . În special, specialistul olandez E. Dijkstra a vorbit în scris cu propuneri de respingere completă a utilizării instrucțiunilor GOTO în toate limbile de nivel înalt. Au fost dezvoltate și tehnici care vizează reducerea volumului de programe și creșterea productivității programatorului și utilizatorului.

Consolidare și dezvoltare

În anii 1980 a început o perioadă care poate fi numită condiționat timpul consolidării. Limbajul C++ a combinat caracteristicile de programare orientată pe obiecte și sisteme, guvernul SUA a standardizat limbajul Ada , derivat din Pascal și destinat utilizării în sistemele de control la bord pentru instalațiile militare, s-au făcut investiții semnificative în Japonia și alte țări a lumii pentru a studia perspectivele așa-numitelor limbaje din generația a cincea, care ar include constructe de programare logică [15] . Comunitatea lingvistică funcțională a adoptat ML și Lisp ca standard. În general, această perioadă s-a caracterizat mai mult prin construirea pe bazele puse în deceniul precedent decât prin dezvoltarea de noi paradigme.

O tendință importantă care a fost observată în dezvoltarea limbajelor de programare pentru sisteme la scară largă a fost concentrarea pe utilizarea modulelor - unități volumetrice de organizare a codului. Deși unele limbi, cum ar fi PL/1, acceptau deja funcționalitatea corespunzătoare, sistemul modular și-a găsit drumul și în limbajele Modula-2 , Oberon , Ada și ML. Adesea sistemele modulare au fost combinate cu constructe de programare generice [16] .

Limbajele de programare vizuală (grafică) devin un domeniu important de lucru , în care procesul de „scriere” a unui program ca text este înlocuit cu procesul de „desen” (proiectarea unui program sub forma unei diagrame) pe ecranul unui computer. Limbile vizuale oferă vizibilitate și o mai bună percepție a logicii programului de către o persoană.

În anii 1990, în legătură cu dezvoltarea activă a Internetului , limbile care vă permit să scrieți pagini web s-au răspândit  - în principal Perl , care s-a dezvoltat dintr-un instrument de scripting pentru sistemele Unix și Java . Popularitatea tehnologiilor de virtualizare a crescut și ea . Aceste modificări, însă, nu au reprezentat nici inovații fundamentale, fiind mai degrabă o îmbunătățire față de paradigmele și limbajele deja existente (în acest din urmă caz, în principal familia C).

În prezent, dezvoltarea limbajelor de programare este în direcția creșterii securității și fiabilității, creând noi forme de organizare modulară a codului și integrare cu baze de date .

Specificația limbii

Standardizare

Au fost create standarde internaționale pentru multe limbaje de programare utilizate pe scară largă . Organizațiile speciale actualizează și publică în mod regulat specificațiile și definițiile formale ale limbii corespunzătoare. În cadrul unor astfel de comitete, dezvoltarea și modernizarea limbajelor de programare continuă și problemele sunt soluționate privind extinderea sau suportul constructelor de limbaj existente și noi.

Alfabetul

Limbajele de programare moderne sunt concepute pentru a utiliza ASCII , adică disponibilitatea tuturor caracterelor grafice ASCII este o condiție necesară și suficientă pentru scrierea oricăror constructe de limbaj. Caracterele de control ASCII sunt utilizate într-un mod limitat: sunt permise doar returul carușului CR, avansul de linie LF și fila orizontală HT (uneori și fila verticală VT și pagina următoare FF).

Primele limbi, care au apărut în perioada caracterelor pe 6 biți , foloseau un set mai limitat. De exemplu, alfabetul Fortran are 49 de caractere (inclusiv spațiu): ABCDEFGHIJKLMNOPQRSTU VWXYZ 0 1 2 3 4 5 6 7 8 9 = + - * / () . , $' :

O excepție notabilă este limbajul APL , care folosește o mulțime de caractere speciale.

Utilizarea caracterelor non- ASCII (cum ar fi caracterele KOI8-R sau caracterele Unicode ) depinde de implementare: uneori sunt permise doar în comentarii și constante de caractere/șir, iar uneori sunt permise și în identificatori. În URSS , au existat limbi în care toate cuvintele cheie au fost scrise cu litere rusești, dar astfel de limbi nu au prea multă popularitate (excepția este limbajul de programare încorporat 1C: Enterprise ).

Extinderea setului de caractere utilizat este constrânsă de faptul că multe proiecte de dezvoltare software sunt internaționale. Ar fi foarte dificil să lucrezi cu un cod în care numele unor variabile sunt scrise cu litere rusești, altele în arabă și altele cu caractere chinezești. În același timp, limbaje de programare de nouă generație ( Delphi 2006 , C# , Java ) acceptă Unicode pentru a lucra cu date text .

Gramatică

Semantică

Există mai multe abordări pentru definirea semanticii limbajelor de programare. Există trei principale: operaționale , axiomatice și denotaționale .

Clasificare

Nu există o taxonomie sistematică general acceptată a limbajelor de programare. Există multe caracteristici conform cărora este posibil să se clasifice limbile, iar unele dintre ele desenează fără echivoc diviziuni între limbi pe baza proprietăților tehnice, altele se bazează pe caracteristici dominante, au excepții și sunt mai condiționate, iar altele sunt complet subiective și adesea însoțite de concepții greșite, dar în practică sunt foarte frecvente.

Un anumit limbaj de programare în marea majoritate a cazurilor are mai mult de un limbaj strămoș. Multe limbi sunt create ca o combinație de elemente din diferite limbi. În unele cazuri, o astfel de combinație este supusă unei analize matematice pentru consecvență (vezi, de exemplu, Definiția standard ML ), în altele, limbajul se formează pe baza unor nevoi practice, pentru a rezolva probleme efective în vederea obținerii succesului comercial, dar fără a respecta matematica. rigoare și cu includerea ideilor care se exclud reciproc în limbaj (ca și în cazul C++ [17] [18] [19] [20] [21] ).

Limbi de nivel scăzut și înalt

De obicei, „nivelul de limbă” se referă la:

Această dualitate a apărut în anii 1950 , odată cu crearea limbilor Plankalkül și Fortran . În timpul dezvoltării lor, intențiile directe au fost stabilite pentru a oferi o înregistrare mai concisă a constructelor întâlnite frecvent (de exemplu, expresii aritmetice) decât era cerută de procesoarele de atunci. Aceste limbaje au introdus un nou strat de abstractizare și trebuiau să transforme programele în limbajul mașină , așa că au fost numite limbaje „de nivel înalt”, adică o suprastructură, un strat deasupra limbajului mașină. Cu toate acestea, curând a devenit clar că aceste definiții nu merg neapărat una lângă alta. Astfel, istoria cunoaște cazuri când un limbaj considerat în mod tradițional „la nivel înalt” a fost implementat în hardware (vezi Lisp Machine , Java Optimized Processor ), sau când un limbaj care este „la nivel scăzut” pe o platformă a fost compilat ca „ de nivel înalt" pe altul (astfel, programele de asamblare VAX CISC au fost folosite pe mașinile DEC Alpha RISC  - vezi VAX Macro ). Astfel, conceptul de nivel de limbă nu este strict formal, ci mai degrabă condiționat.

Limbajele de nivel scăzut includ, în primul rând, limbaje de mașină (sau, în jargonul obișnuit, coduri de mașină), adică limbaje implementate direct la nivel hardware. Ele aparțin primei generații de limbaje de programare . La scurt timp după ele, au apărut limbi de a doua generație  - așa-numitele „ limbi de asamblare ”. În cel mai simplu caz, implementează un mnemonic în limbajul mașinii pentru scrierea comenzilor și a parametrilor acestora (în special, adresele din memorie). În plus, multe limbaje de asamblare includ un limbaj macro foarte dezvoltat . Limbile de prima și a doua generație vă permit să controlați cu precizie modul în care funcționalitatea necesară va fi executată pe un procesor dat, ținând cont de caracteristicile arhitecturii acestuia. Pe de o parte, acest lucru asigură performanța ridicată și compactitatea programelor, dar, pe de altă parte, pentru a transfera un program pe o altă platformă hardware, acesta trebuie să fie recodat (și adesea reproiectat din cauza diferențelor de arhitectură a procesorului) de la zero. Majoritatea limbajelor de asamblare sunt netipizate , dar există și limbaje de asamblare tipizate , menite să ofere securitate minimă pentru programele de nivel scăzut.

Până în anii 1970, complexitatea programelor a crescut atât de mult încât a depășit capacitatea programatorilor de a le gestiona, iar acest lucru a dus la pierderi uriașe și la stagnare în dezvoltarea tehnologiei informației [22] . Răspunsul la această problemă a fost apariția unei mase de limbaje de nivel înalt care oferă o varietate de moduri de a gestiona complexitatea (pentru mai multe detalii, consultați paradigma de programare și limbaje pentru programare la scară mică și mare ). Programele în limbi „la nivel înalt” sunt mult mai ușor de modificat și foarte ușor de transferat de la computer la computer. În practică, limbajele de a treia generație , care pretind doar a fi „nivel înalt”, dar de fapt oferă doar acele constructe „de nivel înalt” care găsesc o corespondență unu-la-unu cu instrucțiunile din mașina von Neumann [23] au primit cea mai răspândită utilizare .

Limbile din a patra generație includ limbi de ordin superior . Uneori se distinge o categorie de limbi de generația a cincea , dar nu este general acceptată - termenul „ limbă de foarte înalt nivel ” este folosit mai des . Acestea sunt limbaje a căror implementare include o componentă algoritmică semnificativă (adică în care interpretarea unui cod sursă mic necesită calcule foarte complexe). Cel mai adesea, aceasta se numește limbaje logice , despre care se spune că sunt doar limbaje de generația a patra, completate de o bază de cunoștințe [24] . În plus, „limbajele de nivel înalt” includ limbaje vizuale și limbi bazate pe un subset de limbaj natural (de exemplu, așa-numita „proză de afaceri”).  

O categorie importantă este limbile specifice domeniului ( DSL - Domain Specific Language ) .  Atribuirea unei limbi în această categorie este foarte arbitrară și adesea controversată; în practică, acest termen poate fi aplicat reprezentanților generațiilor a treia, a patra și a cincea de limbi. Uneori chiar clasifică limbajul C , care poate fi atribuit generației „2.5”. A fost comercializat inițial ca „asamblator de nivel înalt”; este, de asemenea, adesea denumită „limbaj de nivel intermediar”. Vă permite să controlați în mare măsură modul în care este implementat algoritmul, ținând cont de proprietățile tipice unui număr foarte mare de arhitecturi hardware. Cu toate acestea, există platforme pentru care nu există implementări C (chiar într-o formă non-standard) din cauza imposibilității fundamentale sau a inutilității creării lor. De-a lungul timpului, au apărut și alte limbaje de nivel mediu, cum ar fi LLVM , C-- .

Primele trei generații de limbaje formează o paradigmă de programare imperativă , iar generațiile ulterioare formează una declarativă [24] . Termenul „ imperativ ” înseamnă „ordine de comandă”, adică programare prin intermediul instrucțiunilor pas cu pas pentru mașină sau o indicație detaliată a modului în care programatorul a inventat deja pentru a implementa sarcina tehnică. Termenul „ declarator ” înseamnă „descriere”, adică programare prin furnizarea unei formalizări a termenilor de referință într-o formă adecvată transformărilor automate , cu libertatea de alegere pentru traducătorul de limbă . Limbile imperative urmăresc să descrie cum să obțineți un rezultat, în timp ce limbile de nivel superior urmăresc să descrie ceea ce este necesar ca rezultat. Prin urmare, primele sunt numite ca -limbi (sau limbi orientate spre mașină), iar cele din urmă sunt numite ceea ce -limbi (sau limbi orientate spre om). Pentru multe probleme, o generare complet automată a unei implementări cu adevărat eficiente este indecidabilă din punct de vedere algoritmic , așa că în practică, chiar și în ce limbi, se folosesc adesea anumite trucuri algoritmice. Cu toate acestea, există metode pentru obținerea de implementări eficiente din definiții (implementări frontale) - cum ar fi supercompilarea inventată în URSS .

În cele mai multe cazuri, limbajele de nivel înalt produc cod de mașină mai mare și rulează mai lent. Cu toate acestea, unele limbaje de nivel înalt pentru programe complexe din punct de vedere algoritmic și structural pot oferi un avantaj vizibil în eficiență, cedând la cele de nivel scăzut doar în programele mici și simple (a se vedea eficiența limbajului pentru mai multe detalii ). Cu alte cuvinte, potențiala eficiență a unei limbi se modifică odată cu creșterea „nivelului” acesteia în mod neliniar și în general ambiguu. Cu toate acestea, viteza de dezvoltare și complexitatea modificării, stabilității și alți indicatori de calitate în sisteme complexe se dovedesc a fi mult mai importante decât viteza maximă posibilă de execuție - ele oferă o distincție între un program care funcționează și unul care nu [ 25]  - astfel încât evoluția hardware-ului să fie mai fezabilă din punct de vedere economic (executarea mai multor instrucțiuni pe unitatea de timp) și optimizarea metodelor de compilare (mai mult, în ultimele decenii, evoluția hardware s-a îndreptat spre sprijinirea metodelor de optimizare de compilare pentru limbaje de nivel înalt) . De exemplu, colectarea automată a gunoiului , prezentă în majoritatea limbajelor de programare de nivel înalt, este considerată una dintre cele mai importante îmbunătățiri care au un efect benefic asupra vitezei de dezvoltare [26] .

Prin urmare, în zilele noastre, limbajele de nivel scăzut sunt folosite numai în problemele de programare a sistemelor . Se crede larg că în sarcinile în care este necesar un control precis asupra resurselor, limbajul în sine ar trebui să necesite cât mai puține transformări posibil, altfel toate eforturile programatorului vor fi zadarnice. De fapt, există exemple care infirmă acest lucru. Deci, limbajul BitC este un reprezentant al celei de -a patra generații ( paradigma de programare funcțională ), dar este în întregime concentrat pe programarea sistemului și concurează cu încredere în viteză cu C. Adică este un „limbaj de nivel înalt” destinat „programarii la nivel scăzut”. Limbajele de generație a treia C# și Limbo au fost dezvoltate pentru a fi utilizate atât în ​​programarea sistemului (pentru a crește toleranța la erori a sistemului de operare ), cât și în programarea aplicată - acest lucru asigură unitatea platformei, ceea ce reduce pierderile de traducere.

Limbi sigure și nesigure

Calculatoarele moderne reprezintă date complexe din lumea reală ca numere în memoria computerului. Acest lucru introduce riscul erorilor umane în disciplina de programare , inclusiv posibilitatea unor erori de acces la memorie . Prin urmare, multe limbaje de programare sunt însoțite de un mijloc de control al semnificației operațiilor pe date binare pe baza informațiilor logice care le însoțește - un sistem de tip . Cu toate acestea, există și limbi netipizate , cum ar fi Forth .

Sistemele de tip de limbi sunt împărțite în dinamice (descendenții lui Lisp , Smalltalk , APL ) și statice , iar acestea din urmă, la rândul lor, sunt împărțite în nepolimorfe (descendenții lui Algol și BCPL ) și polimorfe (descendenții lui ML ) [ 27] . În plus, ele sunt împărțite în explicit ( engleză  explicit ) și implicit ( engleză  implicită ) - cu alte cuvinte, necesită o declarație explicită a tipurilor pentru obiectele din program sau le deduce static pe cont propriu.

Există sisteme de tip puternic și slab . Un sistem de tip puternic atribuie un tip oricărei expresii o dată pentru totdeauna (ori de câte ori se întâmplă în mod specific - dinamic sau static ), în timp ce un sistem de tip slab vă permite să reatribuiți tipurile mai târziu. Tastarea puternică este uneori identificată în mod eronat cu tastarea statică.

În general, se spune că un limbaj este sigur dacă programele din el, care pot fi acceptate de compilator ca fiind bine formate, nu depășesc niciodată în mod dinamic limitele comportamentului acceptabil [28] . Acest lucru nu înseamnă că astfel de programe nu conțin deloc erori. Termenul „comportament bun al programului” ( ing.  comportament bun ) înseamnă că, chiar dacă programul conține o anumită eroare (în special, o eroare logică ), nu este totuși capabil să încalce integritatea datelor și să se prăbușească ( ing . .  accident ). Deși termenii sunt informali, securitatea unor limbi (de exemplu Standard ML ) este demonstrabilă matematic [27] . Alții (cum ar fi Ada ) au fost asigurați ad-hoc fără a oferi integritate conceptuală, ceea ce poate fi dezastruos dacă este bazat pe sarcini critice (vezi integritatea conceptuală a limbilor ). Terminologia informală a fost popularizată de Robin Milner , unul dintre autorii teoriei verificării formale și ai limbajului Standard ML în sine .

Gradul de control al erorilor și modul în care limbajul reacționează la acestea pot varia. Cele mai simple sisteme de tip interzic, de exemplu, scăderea unui șir dintr -un număr întreg . Cu toate acestea, atât milimetrii , cât și inci pot fi reprezentați ca numere întregi , dar ar fi o eroare logică să scădem inci din milimetri. Sistemele de tip dezvoltat permit (și cele mai dezvoltate forțează) să introducă astfel de informații logice în program. Pentru un computer, este redundant și este complet eliminat atunci când codul de mașină este generat într-un fel sau altul . În special, Standard ML nu permite nicio operațiune asupra datelor, cu excepția celor care sunt permise și formalizate în mod explicit; cu toate acestea, programele de pe acesta se pot termina cu o excepție netratată (de exemplu, când se încearcă împărțirea la zero ). Descendentul său, MLPolyR , garantează, de asemenea, nicio excepție netratată. Astfel de limbi sunt numite „ sigure de tip ”. Java și C# sunt mai puțin stricte și controlează doar scurgerile de memorie , așa că, în contextul lor, folosesc adesea termenul mai restrâns „ siguranța tipului de memorie ” sau (mai des) doar „ siguranța accesului la memorie ” . Limbajele tip puternic dinamic monitorizează comportamentul programelor de-a lungul timpului (ceea ce implică degradarea performanței) și răspund la erori prin lansarea unei excepții. Toate aceste limbaje sunt axate pe uzabilitate , oferind cel mai bun compromis între evitarea eșecurilor grave și viteza mare de dezvoltare a programului. Dar există și limbaje concepute pentru scrierea programelor care sunt corecte prin construcție , adică oferă o garanție că programul executabil va fi identic ca structură și comportament cu specificațiile sale (vezi parametric , tip dependent ). În consecință, programele în astfel de limbi sunt adesea denumite „specificații executabile” (a se vedea corespondența Curry-Howard ). Complexitatea dezvoltării în astfel de limbi crește cu ordine de mărime, în plus, necesită o calificare foarte înaltă a dezvoltatorului, așa că sunt utilizate numai în verificarea formală . Exemple de astfel de limbi sunt Agda , Coq .  

Limbile C și descendentul său C++ sunt nesigure [29] . În programele pe ele, sunt întâlnite pe scară largă situații de slăbire a tastării ( tip casting ) și încălcarea sa directă ( typing joc ) , astfel încât erorile de acces la memorie sunt o normă statistică în ele (dar blocarea programului nu are loc imediat, ceea ce o face greu de găsit locul erorii în cod) . Cele mai puternice sisteme de analiză statică pentru acestea (cum ar fi PVS-Studio [30] [31] ) sunt capabile să detecteze nu mai mult de 70-80% dintre erori, dar utilizarea lor este foarte costisitoare din punct de vedere financiar. Este imposibil să se garanteze în mod fiabil funcționarea fără defecțiuni a programelor în aceste limbi fără a recurge la verificarea formală , care nu numai că este mai scumpă, dar necesită și cunoștințe speciale. C are, de asemenea, descendenți siguri, cum ar fi Cyclone .

Limbajul Forth nu pretinde a fi „sigur”, dar, cu toate acestea, în practică, existența unor programe care pot corupa datele este aproape imposibilă, deoarece un program care conține o eroare potențial periculoasă se blochează chiar de la prima rulare de test, forțând codul sursă. a fi corectat. Comunitatea Erlang a adoptat abordarea „lasă-l să se prăbușească”   ,  care vizează, de asemenea, detectarea timpurie a erorilor .

Limbi compilate, interpretate și încorporate

Există trei moduri fundamental diferite de implementare a limbajelor de programare: compilare , interpretare și inline . Există o concepție greșită comună că modalitatea de implementare este o proprietate specifică limbajului . De fapt, această împărțire este arbitrară într-o anumită măsură. Într-un număr de cazuri, limbajul are o semantică formală orientată spre interpretare, dar toate sau aproape toate implementările sale reale sunt compilatoare, uneori optimizatori foarte eficienți (exemple sunt limbaje din familia ML , cum ar fi Standard ML , Haskell ). Există limbi care estompează liniile dintre interpretare și compilare, cum ar fi Forth .

Compilarea înseamnă că codul sursă al programului este mai întâi convertit în codul țintă ( mașină ) printr-un program special numit compilator  - ca urmare, se obține un modul executabil , care poate fi deja lansat pentru execuție ca program separat. Interpretarea înseamnă că codul sursă este executat direct, comandă cu comandă (uneori cu o pregătire minimă, literalmente după parsarea codului sursă în AST ), astfel încât programul pur și simplu nu poate fi rulat fără un interpret . Încorporarea limbajului poate fi gândită filozofic ca „implementare fără traducere ” în sensul că o astfel de limbă este un subset sintactic și semantic al unei alte limbi, fără de care nu există. Mai precis, limbajele încorporabile adaugă încă patru implementări la cele de mai sus.

Modul firesc de implementare pentru limbaj este determinat de momentul asocierii elementelor programului cu caracteristicile lor. În special, în limbile cu tastare statică , variabilele și alte obiecte ale programului sunt asociate cu tipul de date în etapa de compilare, iar în cazul tastării dinamice  , în etapa de execuție, de regulă, într-un punct arbitrar. în program. Unele proprietăți ale elementelor de limbaj, cum ar fi semnificația operatorilor aritmetici sau cuvintele cheie de control, pot fi deja legate în etapa de definire a limbajului. În alte limbi, este posibil să le reatribuiți (vezi legarea numelui ). Legarea timpurie înseamnă de obicei mai multă eficiență a programului, în timp ce legarea ulterioară înseamnă mai multă flexibilitate, cu prețul unor pași mai lenți și/sau mai complexi [32] . Cu toate acestea, chiar și din cazuri aparent evidente, există excepții - de exemplu, polimorfismul intensional amână procesarea tastării statice până la timpul de execuție, dar nu încetinește, ci crește performanța generală (cel puțin în teorie).

Pentru orice limbaj compilat în mod tradițional (cum ar fi Pascal ), poate fi scris un interpret. Dar multe limbaje interpretate oferă câteva caracteristici suplimentare, cum ar fi generarea dinamică a codului (vezi eval ), astfel încât compilarea lor trebuie să fie dinamică (vezi compilarea dinamică ). Astfel, termenul compus „limbaj + metoda de implementare” în unele cazuri este adecvat. În plus, majoritatea interpreților „puri” moderni nu execută constructe de limbaj direct, ci le compilează într-o reprezentare intermediară de nivel înalt (de exemplu, cu dereferință variabilă și extindere macro ). Cele mai multe limbi interpretate sau compilate în mod tradițional pot fi implementate ca încorporabile , deși nu există multe metalimbaje care ar putea acoperi alte limbi ca subsetul lor ( Lisp este cel mai proeminent reprezentant ).

De regulă, programele compilate rulează mai repede și nu necesită programe suplimentare pentru a fi executate, deoarece sunt deja traduse în limbajul mașinii. În același timp, de fiecare dată când textul programului este modificat, acesta trebuie să fie recompilat, ceea ce încetinește procesul de dezvoltare. În plus, un program compilat poate rula doar pe același tip de computer și, de obicei, sub același sistem de operare, pentru care a fost proiectat compilatorul. Pentru a crea un executabil pentru un alt tip de mașină, este necesară o nouă compilare. Limbile interpretate vă permit să rulați programe imediat după o schimbare și pe diferite tipuri de mașini și sisteme de operare fără efort suplimentar, în timp ce cele omoiconice vă permit să  mutați în mod dinamic un program între diferite mașini fără a întrerupe funcționarea acestuia (cel mai frecvent caz de serializare ), permițându-vă să dezvoltați sisteme de disponibilitate continuă ( vezi și sisteme de înaltă disponibilitate ). Portabilitatea unui program interpretat este determinată doar de disponibilitatea implementărilor de interpret pentru anumite platforme hardware. Cu prețul tuturor acestor lucruri, există pierderi de performanță vizibile; în plus, dacă programul conține o eroare fatală, atunci aceasta nu va fi cunoscută până când interpretul nu va ajunge la locul său în cod (spre deosebire de limbajele sigure static ).

Unele limbaje, cum ar fi Java și C# , se situează între compilat și interpretat. Și anume, programul nu este compilat în limbajul mașinii, ci într-un cod de nivel scăzut independent de mașină, bytecode . Codul octet este apoi executat de mașina virtuală . Pentru a executa bytecode, interpretarea este de obicei utilizată, deși unele dintre părțile sale pot fi traduse în codul mașinii direct în timpul execuției programului folosind compilarea Just-in-time ( JIT ) pentru a accelera programul. Pentru Java, bytecode este executat de Java Virtual Machine ( JVM ), pentru C # - Common Language Runtime . Această abordare, într-un fel, vă permite să utilizați avantajele atât ale interpreților, cât și ale compilatorilor.

Limbi de ordinul întâi și superior

Informații inițiale

Logica matematică este clasificată după ordine  - vezi logica de ordinul întâi și logica de ordin superior . Această terminologie este moștenită în mod natural de informatică , formând semantică, respectiv, de ordinul întâi și superior [33] . Limbile de ordinul întâi (de exemplu, descendenții lui Algol , cum ar fi Basic sau Pascalul clasic al lui Wirth ) permit doar definirea dependențelor de ordinul întâi între cantități. De exemplu, valoarea square xdepinde de valoarea lui x. Astfel de dependențe se numesc funcții . Limbile de ordin superior vă permit să definiți dependențe între dependențe. De exemplu, valoarea map f xdepinde de valori fși x, unde valoarea fîn sine exprimă o dependență abstractă (cu alte cuvinte, parametrul f variază pe un set de funcții cu o anumită semnătură ). Astfel de dependențe sunt numite funcții de ordin superior . În același timp, în cele mai multe cazuri, se spune că un astfel de limbaj consideră dependențele ( funcțiile ) drept obiecte de primă clasă , cu alte cuvinte, permite funcții de primă clasă [34] (unele limbaje, precum C , nu nu acceptă funcții de primă clasă, dar oferă oportunități limitate de a construi funcții de ordin superior). Acești termeni au fost introduși de Christopher Strachey . Limbile de ordin superior includ aproape toate limbajele funcționale (excepțiile sunt foarte rare; un exemplu de limbaj funcțional de ordinul întâi a fost SISAL pentru o lungă perioadă de timp , dar suportul pentru funcții de primă clasă a fost adăugat la acesta în 2018). Odată cu dezvoltarea sistemelor de tip, distincția ordinelor s-a extins la tipuri (vezi constructor de tip ).

Expresivitatea

Limbajele de ordinul întâi permit algoritmilor să fie încorporați în cod , dar nu în arhitectura programului . Potrivit lui Strachey , această restricție a fost moștenită de limba Algol (și de alte limbi din aceasta) din matematica clasică, unde sunt folosite numai operații și funcții constante care sunt recunoscute în mod unic în afara contextului și nu există o notație sistematică. pentru lucrul arbitrar cu funcții (ca o astfel de notație în anii 1930, a fost construit calculul lambda , care a stat mai târziu la baza limbajelor de ordin superior) [35] . Schemele de interacțiune a componentelor ( proceduri , funcții , obiecte , procese etc.) pentru programele în limbaje de ordinul întâi pot exista doar la nivel condiționat, în afara programelor în sine. De-a lungul timpului, s-au descoperit scheme similare de acest fel repetate în mod repetat , în urma cărora a fost construită o metodologie independentă în jurul lor - modele de proiectare . Limbajele de ordin superior permit implementarea unor astfel de scheme ca cod executabil reutilizabil (funcții concepute pentru a transforma și compune alte funcții - vezi, de exemplu, convertoare și scanere în SML ) [36] [37] . Ca urmare, soluțiile care în limbajele de ordinul întâi pot fi reprezentate prin fragmente de program (uneori destul de complexe și greoaie), în limbajele de ordin superior pot fi reduse la o singură comandă sau chiar utilizarea unui element al semantica limbajului propriu-zis care nu are o expresie sintactică. De exemplu, modelul „ Comandă ” , folosit adesea în limbile de ordinul întâi, este direct echivalent cu însăși noțiunea de funcție de primă clasă . Același lucru este valabil și pentru straturile superioare ale limbilor - tastarea (vezi polimorfismul în genurile superioare ) și tastarea tastării (vezi polimorfismul genurilor ).

Cele de mai sus se aplică în principal limbilor a căror semantică se bazează pe calculul lambda (descendenții lui Lisp , ML ). Cu toate acestea, unele limbaje de natură diferită oferă și programare de ordin superior . Exemple sunt limbaje de stivă ( Forth ) și un anumit tip de limbaje orientate pe obiect ( Smalltalk , CLOS , vezi mesajul de ordin superior ).

Explorând

Introducând terminologia „entităților de prima și a doua clasă”, Strachey a subliniat imediat că din experiența personală și discuțiile cu mulți oameni, a fost convins că este incredibil de dificil să nu te mai gândești la funcții ca obiecte de clasa a doua [ 35] . Adică, ordinea limbajului are o influență psihologică pronunțată (vezi ipoteza Sapir-Whorf ). Cunoașterea limbajelor de nivel superior va ajuta programatorul să gândească în termeni de abstracții de nivel superior [38] .

Limbile de nivel scăzut, pe de altă parte, pot impune contrariul, în legătură cu care următoarea afirmație este larg cunoscută:

Este aproape imposibil să predați programare bună studenților care au avut experiență BASIC: ca potențiali programatori, ei sunt deformați mental, fără nicio speranță de recuperare.

Text original  (engleză)[ arataascunde] Este practic imposibil să predați programare bună studenților care au avut o expunere anterioară la BASIC: ca potențiali programatori, ei sunt mutilați mental dincolo de speranța de regenerare. — Edsger Dijkstra

Aceasta înseamnă că folosirea unui limbaj de ordin superior nu înseamnă automat o schimbare a arhitecturii și o creștere a reutilizării (vezi nici un glonț de argint ) - factorul determinant este capacitatea unui anumit dezvoltator de a folosi expresiile adecvate [39] .

Înțelegând posibilitățile și limitările constructelor de nivel înalt, principiile de bază ale implementării lor nu numai că oferă programatorului posibilitatea de a utiliza cel mai eficient limbajul pe care l-a învățat, dar îi permit și să creeze și să utilizeze mecanisme similare în cazul dezvoltării într-un limba unde nu sunt implementate [38] .

Va fi mai ușor pentru un dezvoltator care deține o gamă mai largă de limbaje de programare să aleagă dintre ele instrumentul care este cel mai potrivit pentru rezolvarea sarcinii în fața lui, să învețe, dacă este necesar, o nouă limbă sau să implementeze un limbaj specific domeniului , care, de exemplu, includ o interfață de linie de comandă un program destul de complicat [40] .

Paradigma de programare

Atribuirea limbilor la paradigme se poate face pe mai multe motive, dintre care unele corespund unor caracteristici tehnice specifice ale limbilor, în timp ce altele sunt foarte condiționate.

Din punct de vedere tehnic, limbile sunt împărțite, de exemplu, în care permit efecte secundare și transparent referențial . În al doilea caz, se spune că limbajul aparține unei „ paradigma pur funcționale ”. Anumite proprietăți ale sistemului de tip și strategii de evaluare a limbajului sunt, de asemenea, considerate uneori ca paradigmă , de exemplu, pentru sistemele de tip parametric polimorfe , se vorbește adesea despre implementarea paradigmei de programare generică . Un alt exemplu este proprietatea homoiconicității , care deschide o gamă întreagă de varietăți de metaprogramare . Există multe „limbi moștenite de la matematică” , dintre care multe formează paradigme unice. Reprezentanți proeminenți sunt Lisp , care a întruchipat pentru prima dată calculul lambda și a pus astfel bazele paradigmei funcționale , Smalltalk , care a întruchipat mai întâi paradigma orientată pe obiect ( Simula care a apărut cu mulți ani înainte de a susține conceptul de clasă , dar a întruchipat paradigma structurală ) și limbajul Forth stack , care întruchipează paradigma concatenative .

Mai convențional, limbile sunt împărțite în generații . Primele două generații sunt de nivel scăzut , adică concentrate pe specificul hardware-ului specific și, în principiu, nu corespund nici unei paradigme (deși un anumit dezvoltator de pe ele, desigur, poate urma ideologic anumite tendințe). Împreună cu a treia generație, ele formează o paradigmă de programare imperativă , iar generațiile ulterioare - una declarativă ( pentru mai multe detalii, vezi secțiunea Limbaje de nivel scăzut și înalt ). Multe limbi declarative includ anumite caracteristici imperative, uneori invers.

Odată cu creșterea dimensiunii și complexității programelor, utilizând deja limbaje de a doua generație, a început să se formeze paradigma programării procedurale , necesitând descompunerea procedurilor mari într-un lanț de proceduri mai mici legate ierarhic. Aproximativ în același timp, au apărut primele limbaje din a treia generație și s-a format prima programare structurată ca o dezvoltare directă a procesului , apoi modular . De-a lungul timpului, au apărut un număr imens de moduri diferite de a rezolva problema complexării sistemelor software în creștere , menținând în același timp abordarea imperativă inițială la bază. În unele cazuri, s-a atins un impact semnificativ asupra vitezei de dezvoltare și a indicatorilor de calitate, dar, în general, după cum s-a menționat mai sus , limbajele din a treia generație se abțin din logica mașinii doar la un anumit nivel și sunt ușor supuse transformărilor echivalente . . Până în prezent, a treia generație de limbi este reprezentată de cea mai extinsă gamă de paradigme diferite.

A patra generație include limbaje funcționale , din care „pur funcțional” ( ing.  pur funcțional , corespunzătoare categoriei tehnice menționate mai sus de transparent referențial ), iar restul se numesc „nu pur funcțional” ( ing.  pur funcțional ) ).

Generația a cincea include limbaje de programare logică , în care, pe lângă cel tradițional, se disting mai multe forme speciale, de exemplu, programarea cu constrângeri . De fapt, limbile din generația a cincea sunt limbi din generația a patra, completate de o bază de cunoștințe [24]  - prin urmare, această categorie, așa cum sa menționat mai sus, nu este în general acceptată.

Multe paradigme sunt metode declarate condiționat de organizare a structurii unui program și sunt aplicabile într-o mare varietate de limbi. Structurale și modulare au cea mai largă acoperire  - sunt folosite atât în ​​limbaje imperative , cât și în limbaje declarative . Alte paradigme sunt strâns legate de proprietățile tehnice. De exemplu, un subset al limbajului C++ - șabloane  - poate fi considerat formal ca un limbaj pur funcțional complet Turing , dar C++ nu are proprietățile inerente limbajelor funcționale ( transparență referențială , siguranță tip , apel de coadă ). garanție de optimizare etc.). În consecință, algoritmii utilizați în compilarea limbajelor funcționale nu pot fi aplicați la C++ și, prin urmare, cercetătorii de frunte ai paradigmei funcționale sunt foarte sceptici cu privire la C++ (pentru mai multe detalii, consultați critica șabloanelor C++ ).

Limbaje pentru programare la scară mică și mare

Programele pot rezolva probleme de diferite scări : un program construiește un program pentru o anumită funcție, iar celălalt gestionează fluxul de lucru al unei întreprinderi mari. Diferite limbaje de programare sunt concepute pentru diferite scări inițiale de sarcini și, mai important, gestionează creșterea complexității sistemelor software în moduri diferite. Calitatea cheie a limbajului, care determină modul în care se schimbă complexitatea dezvoltării pe măsură ce sistemul crește, este abstractizarea , adică capacitatea de a separa sensul (comportamentul) unei componente ale sistemului de modul în care este implementată [41] [42 ]. ] .

Creșterea complexității oricărui sistem software este limitată fundamental de limita până la care este încă posibil să se mențină controlul asupra acestuia: dacă cantitatea de informații necesară pentru a înțelege o componentă a acestui sistem depășește „capacitatea” creierului unuia. persoană, atunci această componentă nu va fi pe deplin înțeleasă. Va deveni extrem de dificil să-l rafinați sau să corectați erori și se poate aștepta ca fiecare corectare să introducă noi erori din cauza acestor cunoștințe incomplete.

Text original  (engleză)[ arataascunde] Există o limită fundamentală a complexității oricărui sistem software pentru ca acesta să fie încă gestionabil: dacă necesită mai mult de „un minut” de informații pentru a înțelege o componentă a sistemului, atunci acea componentă nu va fi înțeleasă pe deplin. Va fi extrem de dificil să faceți îmbunătățiri sau să remediați erori și fiecare remediere este probabil să introducă erori suplimentare din cauza acestor cunoștințe incomplete. — Martin Ward, „Programare orientată pe limbă” [43]

Indicatorii de calitate a codului sursă, cum ar fi testabilitatea și modificarea, sunt în mod evident determinați de factorul de reutilizare . Aceasta poate însemna atât aplicarea de funcții diferite la aceeași componentă, cât și posibilitatea de a aplica aceeași funcție la diferite componente. Sistemele de tip parametric polimorfe (în special inferenţiale ) şi dinamice cresc foarte mult factorul de reutilizare : de exemplu, o funcţie care calculează lungimea unui tablou va fi aplicabilă unui număr infinit de tipuri de matrice [27] [44] . Dacă limbajul cere în semnătura funcției să indice un mod specific de implementare a datelor de intrare, atunci acest coeficient suferă puternic. De exemplu, Pascal a fost criticat pentru că trebuie să specifice întotdeauna o dimensiune specifică a matricei [45] , iar C++ a fost criticat pentru că trebuie să  facă distincție atunci când se referă la componentele de date compozite [46] . Limbile de ordin superior \u003e vă permit să evidențiați schemele de interacțiune ale funcțiilor într-un bloc de cod numit în mod repetat ( funcție de ordin superior ) [36] [47] , iar reutilizarea atinge cele mai mari valori atunci când trecerea la un limbaj de nivel superior - dacă este necesar, special dezvoltat pentru o anumită sarcină  - în acest caz, limbajul este reutilizat mai degrabă decât o singură funcție [43] , iar dezvoltarea limbajului în sine poate fi realizată cu reutilizare intensivă a componentelor compilatorului [48] . .->

Odată cu dezvoltarea limbajelor au apărut categorii speciale (inerente doar programarii, nenecesare anterior matematicii) categorii de componente și dependențe: monade , clase de tip , ramuri polimorfe , aspecte etc. Utilizarea lor vă permite să exprimați mai multă funcționalitate în aceeași cantitate. de cod, traducând astfel programarea - mare la o scară mai mică.

Alte probleme fundamentale asociate cu complexitatea sistemelor mari se află în afara programelor în sine: aceasta este interacțiunea programatorilor care le dezvoltă între ei, documentare etc. Pe lângă furnizarea de abstractizare , integritatea conceptuală a limbajului de programare ales. joacă un rol semnificativ în aceasta [49] [43] .

Pe lângă proprietățile semanticii limbajului, reutilizarea poate fi asigurată prin structura modulară a unui sistem software sau complex. Mai mult, indiferent cât de flexibil este limbajul, lucrul cu cantități uriașe de cod, în special cu mulți oameni, necesită ca acestea să fie descompuse în module într-un fel sau altul. Structura modulară presupune nu doar împărțirea unui cod sursă de program monolitic în mai multe fișiere text, ci oferirea unei abstracțiuni la scară mai mare, adică definirea unei interfețe pentru orice fragment logic complet și ascunderea detaliilor implementării acestuia. În funcție de regulile de stabilire a domeniului aplicate în limbă, limbajul poate sau nu permite definirea automată a dependenței. Dacă, conform regulilor , este posibil un conflict de nume, atunci detectarea automată a dependențelor este imposibilă, iar în antetul modulului este necesar să se listeze în mod explicit numele modulelor ale căror componente sunt utilizate în acesta.

Unele limbaje (cum ar fi Basic sau Pascalul clasic al lui Wirth ) sunt concentrate exclusiv pe dezvoltarea de programe mici, simple din punct de vedere structural. Ele nu oferă nici un sistem dezvoltat de module, nici flexibilitatea unor fragmente specifice. Limbajul C a fost creat ca un „asamblator de nivel înalt”, ceea ce în sine nu implică dezvoltarea sistemelor peste un anumit prag de complexitate, astfel încât nici suportul pentru programarea la scară largă nu a fost inclus în el. Unele limbaje de nivel înalt și de nivel ultra-înalt ( Erlang , Smalltalk , Prolog ) oferă ca elemente primitive de bază concepte care în alte limbi sunt complexe din punct de vedere structural și algoritmic ( procese , clase , baze de cunoștințe) - similare cu diversele matematice calculi ( vezi și integritatea conceptuală a limbajelor ). Prin urmare, astfel de limbi sunt adesea considerate ca fiind specifice domeniului  - unele (dar nu toate) sarcinile par simple în ele, care par complexe în alte limbi. Cu toate acestea, extinderea funcționalității în alte moduri în aceste limbi poate fi dificilă. ML standard și rudele sale sunt stratificate în două limbi, dintre care una - " limbaj de bază " ( ing.  limbajul de bază ) - se concentrează pe dezvoltarea de programe simple, iar cealaltă - " limbajul modulului " ( ing.  limbajul modulului ), - respectiv, pe aranjarea neliniară a acestora în sisteme software complexe. De-a lungul timpului, au fost create opțiuni pentru a le îmbina împreună ( 1ML ). Multe alte limbi includ, de asemenea , sisteme de module , dar cele mai multe sunt limbaje de modul de ordinul întâi . Limbajul modulului ML este singurul limbaj al modulului de ordin superior de acest fel . Limbile Lisp și Forth vă permit să creșteți sistemele în mod arbitrar și nelimitat, inclusiv permițându-vă să creați limbaje încorporabile specifice domeniului în interiorul lor (ca subsetul lor sintactic și semantic) - prin urmare, ele sunt adesea numite metalimbaje .

Cea mai populară abordare pentru rezolvarea problemei complexării astăzi este programarea orientată pe obiecte , deși succesul aplicării sale de-a lungul deceniilor de existență a fost supus în mod repetat scepticismului și încă nu există dovezi sigure că aduce beneficii în comparație cu alte abordări. în ceea ce priveşte fie alţi indicatori de calitate . Este însoțit (și uneori concurează) de diverse tehnologii de reglare a dependențelor dintre componente: metaclase , contracte , prototipuri , mixine , trăsături etc.

O abordare mai puternică a fost considerată istoric utilizarea diferitelor forme de metaprogramare , adică automatizarea procesului de dezvoltare în sine la diferite niveluri. Există o diferență fundamentală între metaprogramarea externă limbii și disponibilă în limbajul însuși. Atunci când se utilizează limbaje de ordinul întâi complexitatea sistemelor software în creștere depășește rapid pragul abilităților unei persoane în perceperea și procesarea informațiilor, prin urmare, mijloace externe de proiectare vizuală preliminară sunt utilizate pentru a studia scheme complexe pe o formă simplificată. formular și apoi generează automat un cadru de cod - vezi ..CASE În comunitățile de dezvoltatori care utilizează limbaje de ordin superior , domină abordarea opusă - pentru a preveni însăși posibilitatea ca complexitatea să scape de sub control prin împărțirea modelelor de informații în componente independente și dezvoltarea instrumentelor pentru conversia automată a unui model în altul - vezi limba -programare orientata .

Integritatea conceptuală a limbilor

Frederick Brooks [50] și C. E. R. Hoare [51] subliniază necesitatea asigurării integrității conceptuale a sistemelor informaționale în general și a limbajelor de programare în special, astfel încât fiecare parte a sistemului să folosească forme sintactice și semantice similare și să nu fie nevoie să fi stăpânit pe lângă sistemul propriu-zis de compunere precum și regulile de utilizare idiomatică a acestuia . Hoare a prezis că complexitatea Adei va provoca catastrofe. Alan Kay distinge limbile care sunt „ cristalizarea stilului   de alte limbi care sunt „ aglutinarea trăsăturilor ” [ 52] . Greg Nelson [53] și Andrew Appel [ 27] pun limbile derivate matematic într -o categorie specială .  

Aceste accentuări necesită utilizarea unor limbaje care întruchipează un fel de calcul matematic, adaptat cu grijă pentru a fi un limbaj mai practic pentru dezvoltarea de programe reale. Astfel de limbi sunt ortogonale și, deși acest lucru înseamnă că multe dintre idiomurile comune care sunt disponibile ca primitive de limbă în limbi mai populare trebuie implementate manual, expresivitatea unor astfel de limbi poate fi în general substanțial mai mare.

Doar câteva limbi se încadrează în această categorie; majoritatea limbilor sunt concepute cu prioritate pentru o traducere eficientă pe o mașină Turing . Multe limbi se bazează pe teorii generale, dar în timpul dezvoltării ele nu sunt aproape niciodată testate pentru siguranța partajării unor elemente specifice de limbaj care sunt aplicații speciale ale acestor teorii, ceea ce duce inevitabil la incompatibilitatea între implementările limbajului. Aceste probleme fie sunt ignorate, fie sunt prezentate ca un fenomen natural ( ing.  „nu un bug, ci o caracteristică” ), dar în realitate sunt cauzate de faptul că limbajul nu a fost supus analizei matematice [54] .

Exemple de limbaje bazate pe matematică și modelele matematice pe care le implementează:

A avea o bază matematică pentru o limbă poate garanta (sau cel puțin promite cu o probabilitate foarte mare) unele sau toate dintre următoarele proprietăți pozitive:

  • Creștere semnificativă a stabilității programului. În unele cazuri, prin construirea unei dovezi de fiabilitate pentru limbajul propriu-zis (vezi tip siguranța ), simplificând semnificativ verificarea formală a programelor și chiar obținerea unui limbaj care este el însuși un sistem de demonstrare automată ( Coq , Agda ). În alte cazuri, din cauza detectării timpurii a erorilor la primele rulări de probă ale programelor ( Forth și expresii regulate ).
  • Asigurarea unei eficiențe potențial mai ridicate a programului. Chiar dacă semantica limbajului este departe de arhitectura platformei de compilare țintă, i se pot aplica metode formale de analiză globală a programelor (deși complexitatea scrierii chiar și a unui traducător banal poate fi mai mare). De exemplu, pentru limbajele Scheme și Standard ML , sunt dezvoltate compilatoare și supercompilatoare de optimizare a programului complet , al căror rezultat poate concura cu încredere în viteză cu limbajul C de nivel scăzut și chiar îl depășește pe acesta din urmă (deși consumul de resurse al compilatorii înșiși se dovedesc a fi mult mai mari). Unul dintre cele mai rapide DBMS -uri  , KDB [57]  , este scris în limbajul K. Limbajul Scala (care a moștenit matematica de la ML ) oferă o viteză mai mare pe platforma JVM decât limbajul său nativ Java . . Pe de altă parte, Forth are reputația de a fi unul dintre cele mai mari limbi de resurse (mai puțin solicitant decât C ) și este folosit pentru a dezvolta aplicații în timp real pentru cele mai mici computere; în plus, compilatorul Forth este unul dintre cele mai puțin consumatoare de timp de implementat în assembler .
  • O limită cunoscută anterior (nelimitată sau, dimpotrivă, clar definită) a creșterii complexității componentelor software, sistemelor și complexelor care pot fi exprimate folosind acest limbaj, păstrând în același timp indicatorii de calitate [27] [58] . Limbajele care nu au o justificare matematică (și anume, acestea sunt cele mai frecvent utilizate în mainstream : C++ , Java , C# , Delphi , etc.), în practică, limitează funcționalitatea implementată și/sau reduc calitatea ca sistemul devine mai complex [59] , deoarece sunt curbe de creștere exponențială inerente ale complexității, atât în ​​raport cu munca unei persoane individuale, cât și în raport cu complexitatea gestionării proiectului în ansamblu [49] [60] . Complexitatea prezisă a sistemului duce fie la o descompunere etapizată a proiectului în multe sarcini mai mici, fiecare dintre acestea fiind rezolvată de limbajul corespunzător, fie la programare orientată pe limbaj pentru cazul în care sarcina abordată de limbaj este tocmai descrierea. de calcule semantice și/sau simbolice ( Lisp , ML , Haskell , Refal , Expresii regulate ). Limbile cu o limită de creștere nelimitată a complexității programelor sunt adesea denumite metalimbi (ceea ce nu este adevărat în interpretarea directă a termenului, dar este reductibil în practică, deoarece orice mini-limbaj ales pentru a rezolva o anumită limbă). subsarcina ca parte a unei sarcini generale poate fi reprezentată ca un subset sintactic și semantic dat limbaj fără a necesita o traducere [61] ).
  • Comoditate pentru o persoană în rezolvarea problemelor pentru care acest limbaj este orientat prin natură (vezi limbajul specific domeniului ), care, într-o oarecare măsură, poate afecta și (indirect) creșterea stabilității programelor rezultate prin creșterea probabilității de detectare. erori în codul sursă și reducerea dublării codului.

Categorii speciale de limbi

Transformări formale și optimizare

VF Turchin notează [62] că avantajele oricărui limbaj formalizat sunt determinate nu numai de cât de convenabil este pentru utilizare directă de către o persoană, ci și de măsura în care textele în această limbă sunt susceptibile de transformări formale.

De exemplu, transparența referențială înseamnă că nu este necesar ca parametrii funcției să fie evaluați înainte de a fi apelați - în schimb, expresia reală transmisă poate fi complet înlocuită cu variabila din funcție, iar comportamentul funcției nu se va schimba de la aceasta. Acest lucru deschide posibilitatea transformărilor automate aproape arbitrare ale programelor : pot fi eliminate reprezentările de date intermediare inutile, pot fi reduse lanțuri complexe de calcule, poate fi selectat numărul optim de procese paralele, se poate introduce memorizarea etc. De asemenea, aceasta înseamnă o absență completă a efectelor secundare , iar acest lucru face ca implementarea unor algoritmi este notoriu mai puțin eficientă decât atunci când se utilizează starea mutabilă .

Pentru programele mici și simple, limbajele de nivel înalt produc cod de mașină mai mare și rulează mai lent. Cu toate acestea, pentru programele complexe din punct de vedere algoritmic și structural, avantajul poate fi de partea unor limbaje de nivel înalt, deoarece o persoană nu este capabilă fizic să exprime concepte complexe, ținând cont de execuția lor eficientă într-un limbaj de mașină. De exemplu, există un punct de referință în care MLton și Stalin Scheme sunt cu încredere înaintea GCC . Există multe motive speciale pentru care optimizarea automată în timpul traducerii limbilor de nivel înalt oferă , în principiu , o viteză de execuție mai mare decât controlul conștient asupra metodei de implementare în limbile de nivel scăzut. De exemplu, există dovezi bune că gestionarea automată a memoriei este mai eficientă decât gestionarea manuală a memoriei numai atunci când se utilizează o metodă dinamică (vezi colectarea gunoiului ) [63] , dar există și o metodă statică potențial mai eficientă (vezi gestionarea memoriei bazată pe regiune). ). În plus, pentru fiecare micro-context, este necesar să se aloce registre , ținând cont de minimizarea accesului la memorie, iar acest lucru necesită rezolvarea problemei de colorare a graficului . Există o mulțime de astfel de caracteristici ale logicii mașinii, astfel încât complexitatea generală a informațiilor crește exponențial cu fiecare „coborare a unui nivel”, iar compilarea unui limbaj de nivel înalt poate include zeci de astfel de pași.

Există multe strategii de optimizare automată . Unele sunt universale, altele se pot aplica doar limbilor de o anumită natură, iar unele depind de modul în care este folosită limba. Un exemplu este optimizarea apelului de coadă și optimizarea specială a recursiunii case-tail . Deși compilatoarele din multe limbi implementează optimizarea recursiunii de coadă în anumite condiții, doar câteva limbi sunt capabile să garanteze semantic optimizarea apelului de coadă în cazul general. Standardul de limbaj Scheme necesită fiecare implementare pentru a-l garanta. Pentru multe limbaje funcționale , este în principiu aplicabil, dar doar compilatorii de optimizare îl implementează. În limbaje precum C sau C++ , se poate face numai în anumite cazuri și numai atunci când se utilizează analiza globală a fluxului de control [64] .

Limbile de ordin superior tind să ruleze mai lent decât limbile de ordinul întâi în majoritatea cazurilor. Motivele constă atât în ​​descompunerea codului liniar într-un lanț de apeluri imbricate, cât și în caracteristicile rezultate ale reprezentării la nivel scăzut a funcțiilor (vezi închiderea ) și a datelor (înfășurate ( în casetă engleză  ), etichetate). Cu toate acestea, există tehnici agresive de optimizare a programelor care permit ca limbajele de ordin superior să fie reduse la limbaje de ordinul întâi (vezi defuncționalizarea , MLton , Schema Stalin ).

Popularitatea limbilor

Este dificil de determinat care limbaj de programare este cel mai popular, deoarece sensul cuvântului „popularitate” depinde de context (în engleză se folosește termenul „utilizare”, care are un sens și mai vag). O limbă poate lua cele mai multe ore de muncă , o alta are cele mai multe linii de cod, o a treia necesită cel mai mult timp CPU , iar a patra este cea mai mare bază de cercetare din mediul academic. Unele limbi sunt foarte populare pentru sarcini specifice. De exemplu, Cobol încă domină centrele de date ale întreprinderilor , Fortran domină  aplicațiile științifice și de inginerie, variațiile limbajului C  domină programarea sistemelor și diverși descendenți ai ML  domină verificarea formală . . Alte limbi sunt utilizate în mod regulat pentru a crea o mare varietate de aplicații.

Există diverse măsurători pentru măsurarea popularității limbilor, fiecare dintre acestea fiind proiectată cu o părtinire către un anumit sens al conceptului de popularitate:

  • numărarea numărului de posturi vacante care menționează limba;
  • numărul de cărți vândute (manuale sau cărți de referință);
  • o estimare a numărului de linii de cod scrise în limbă (care nu ia în considerare cazurile de utilizare rar publicate ale limbilor);
  • numărarea mențiunilor de limbă în interogările motoarelor de căutare .

Trebuie remarcat faptul că scorurile ridicate pentru acești indicatori nu numai că nu indică în niciun fel un nivel tehnic ridicat al limbajului și/sau optimizarea costurilor la utilizarea acestuia, ci, dimpotrivă, uneori pot indica contrariul. De exemplu, limbajul Cobol este unul dintre liderii în ceea ce privește numărul de linii de cod scrise în el, dar motivul pentru aceasta este rata extrem de scăzută de cod modificabil, ceea ce face ca acest cod să nu fie reutilizabil , ci cod moștenit . Drept urmare, întreținerea programelor Cobol este mult mai costisitoare pe termen scurt decât programele din majoritatea limbilor moderne, dar rescrierea lor de la zero ar necesita o investiție semnificativă unică și poate fi comparată doar cu costul pe termen lung. Imperfecțiunea tehnică a lui Cobol se datorează faptului că a fost dezvoltat fără implicarea experților în domeniul informaticii [65] [66] .

Vezi și

Note

  1. ISO/IEC/IEEE 24765:2010 Ingineria sistemelor și software-ului - Vocabular
  2. ISO/IEC 2382-1:1993, Tehnologia informației - Vocabular - Partea 1: Termeni fundamentali
  3. Lista limbajelor de programare  (engleză)  (link inaccesibil) . Consultat la 3 mai 2004. Arhivat din original pe 12 iunie 2004.
  4. Rojas, Raul , et al. (2000). „Plankalkül: primul limbaj de programare la nivel înalt și implementarea acestuia”. Institut für Informatik, Freie Universität Berlin, Raport tehnic B-3/2000. (text complet) Arhivat 18 octombrie 2014 la Wayback Machine
  5. Computer Languages, 1989 , 1. Constructor invizibil § Realizarea de coduri care pot fi citite de om, p. 16.
  6. Linda Null, Julia Lobur, Elementele esențiale ale organizării și arhitecturii computerelor , Ediția a 2-a, Jones & Bartlett Publishers, 2006, ISBN 0-7637-3769-0 , p. 435
  7. O'Reilly Media. Istoria limbajelor de programare (PDF)  (link indisponibil) . Consultat la 5 octombrie 2006. Arhivat din original la 28 februarie 2008.
  8. Frank da Cruz. Carduri perforate IBM Universitatea din Columbia Istorie de calcul .
  9. Richard L. Wexelblat: History of Programming Languages , Academic Press, 1981, capitolul XIV.
  10. Pratt, 1979 , 4.6. Potrivirea modelului, p. 130-132.
  11. Pratt, 1979 , 15. Snobol 4, p. 483-516.
  12. Pratt, Zelkowitz, 2002 , 8.4.2. Potrivirea modelului, p. 369-372.
  13. Francois Labelle. Graficul de utilizare a limbajului de programare (link indisponibil) . sourceforge . Consultat la 21 iunie 2006. Arhivat din original pe 17 iunie 2006. 
  14. Hayes, Brian.  Războiul punctului  și virgulă // Om de știință american :revistă. - 2006. - Vol. 94 , nr. 4 . - P. 299-303 .
  15. Tetsuro Fujise, Takashi Chikayama, Kazuaki Rokusawa, Akihiko Nakase (decembrie 1994). „KLIC: O implementare portabilă a KL1” Proc. din FGCS '94, ICOT Tokyo, decembrie 1994. http://www.icot.or.jp/ARCHIVE/HomePage-E.html Arhivat la 25 septembrie 2006 la Wayback Machine KLIC este o implementare portabilă a unui limbaj de programare logică simultană KL1 .
  16. Jim Bender. Mini-Bibliografie pe module pentru limbaje funcționale de programare (link indisponibil) . ReadScheme.org (15 martie 2004). Consultat la 27 septembrie 2006. Arhivat din original pe 24 septembrie 2006. 
  17. Stroustrup, Bjarne Evoluția unui limbaj în și pentru lumea reală: C++ 1991-2006 .
  18. T. Pratt, M. Zelkowitz. Limbaje de programare. Dezvoltare și implementare. - 4. - Sankt Petersburg: Petru, 2002. - S. 203. - 688 p. - 4000 de exemplare.  - ISBN 5-318-00189-0 .
  19. Stroustrup B. Designul și evoluția C++ . - Sankt Petersburg: Peter, 2006. - S. 74-76. — 448 p. - 2000 de exemplare.  — ISBN 5-469-01217-4 .
  20. Seibel - Coders at Work, 2011 , capitolul 12. Ken Thompson, p. 414.
  21. Zuev E.A., Krotov A.N., Sukhomlin V.A. Limbajul de programare C++: Stadii de evoluție și starea curentă (4 octombrie 1996). Data accesului: 16 ianuarie 2017.
  22. Paulson, „ML for the Working Programmer”, 1996 , p. 213.
  23. Paulson, „ML for the Working Programmer”, 1996 , p. unu.
  24. 1 2 3 Mernik, 2012 , p. 2-12.
  25. Paulson, „ML for the Working Programmer”, 1996 , p. 9.
  26. Rick Byers. Algoritmi de colectare a gunoiului . cursuri.cs.washington.edu. - Proiect pentru CSEP 521, iarna 2007. Consultat la 28 decembrie 2016.
  27. 1 2 3 4 5 Appel - A Critique of Standard ML, 1992 .
  28. Harper - Practical Foundations for Programming Languages, 2012 , Capitolul 4. Statica, p. 35.
  29. Mitchel, 2004 , 6.2.1 Tip Safety, p. 132-133.
  30. Comparația analizoarelor de cod static: CppCat, Cppcheck, PVS-Studio și Visual Studio
  31. Compararea PVS-Studio cu alte analizoare de cod
  32. Pratt, 1979 , 2.7. Timp de legare și legare, s. 46-51.
  33. Reynolds, „Teoriile limbajelor de programare”, 1998 , 12.4 Deriving a First-Order Semantics.
  34. Strachey - Concepte fundamentale, 1967 , 3.5.1. Obiecte de clasa întâi și a doua., p. 32-34.
  35. 1 2 Strachey - Concepte fundamentale, 1967 , 3.5.1. Obiecte de clasa întâi și a doua, p. 32-34.
  36. 12 SICP . _
  37. Harper - Practical Foundations for Programming Languages, 2012 , 8.2 Higher-Order Functions, p. 67.
  38. 1 2 Pratt, Zelkowitz, 2002 , 1.1 De ce să înveți limbaje de programare, p. 17-18.
  39. Bruce A. Tate. Cuvânt înainte // Șapte limbi în șapte săptămâni: un ghid pragmatic pentru învățarea limbajelor de programare . - Raft pragmatic, 2010. - P.  14-16 . — ISBN 978-1934356593 .
  40. Pratt, Zelkowitz, 2002 , 1.1 De ce să înveți limbaje de programare, p. optsprezece.
  41. Aho, Ulman, 1992 .
  42. Joyner, 1996 , 2.2 Comunicare, abstractizare și precizie, p. patru.
  43. 1 2 3 Ward, 1994 .
  44. Paulson, „ML for the Working Programmer”, 1996 , p. 63-64.
  45. Kernigan despre Pascal, 1981 .
  46. Joyner, 1996 , 3.17'.' și „->”, p. 26.
  47. Paulson, „ML for the Working Programmer”, 1996 , p. 177-178.
  48. Hudak, 1998 .
  49. 1 2 Brooks, 1975, 1995 .
  50. Brooks, 1975, 1995 , Achieving Conceptual Integrity, p. treizeci.
  51. CAR Hoare - Veșmintele vechi ale împăratului, Comunicări ale ACM, 1981
  52. Alan Kay . Istoria timpurie a Smalltalk . — Apple Computer, ACM SIGPLAN Notices, vol.28, nr.3, martie 1993.
  53. Greg Nelson. Programarea sistemelor cu Modula-3. - NJ: Prentice Hall, Englewood Cliffs, 1991. - 288 p. — ISBN 978-0135904640 .
  54. Comentariu la SML, 1991, Aims of the Commentary , p. vii.
  55. Thomas Noll, Chanchal Kumar Roy. Modelarea Erlang în Pi–Calculus . - ACM 1-59593-066-3/05/0009, 2005.
  56. Principiile de proiectare din spatele Smalltalk
  57. kx: Performanță calibrată
  58. Luca Cardelli. Programare tip . — Rapoartele IFIP de ultimă generație, Springer-Verlag, 1991.
  59. Ward, 1994 : „Există o limită fundamentală a complexității oricărui sistem software pentru ca acesta să fie încă gestionabil: dacă necesită mai mult de „un minut” de informații pentru a înțelege o componentă a sistemului, atunci acea componentă nu va fi înțeleasă . in totalitate. Va fi extrem de dificil să faci îmbunătățiri sau să remediezi erori și fiecare remediere este probabil să introducă erori suplimentare din cauza acestor cunoștințe incomplete.”
  60. Glass, 2004 .
  61. Czarnecki et al, 2004 .
  62. Turchin V. F. Transformări echivalente ale programelor pe REFAL: Proceedings of TsNIPIASS 6: TsNIPIASS, 1974.
  63. B. Zorn. Costul măsurat al colectării conservatoare a gunoiului. Raport Tehnic CU-CS-573-92. // Universitatea din Colorado la Boulder. - 1993. - doi : 10.1.1.14.1816 .
  64. Ehud Lamm .
  65. Richard L. Conner. Cobol, vârsta ta se arată  // Computerworld  :  revistă. — Grupul internațional de date, 1984. - 14 mai ( vol. 18 , nr. 20 ). — P.ID/7—ID/18 . — ISSN 0010-4841 .
  66. Robert L. Mitchell. Cobol: Încă nu a murit . Computerworld (4 octombrie 2006). Preluat: 27 aprilie 2014.

Literatură

  • Gavrikov M. M., Ivanchenko A. N., Grinchenkov D. V. Fundamente teoretice pentru dezvoltarea și implementarea limbajelor de programare. - KnoRus , 2013. - 178 p. - ISBN 978-5-406-02430-0 .
  • Krinitsky N. A., Mironov G. A., Frolov G. D. Programare. - GIFML, 1963. - 384 p.
  • Bratchikov I. L. Sintaxa limbajelor de programare. - Stiinta , 1975. - 230 p.
  • Lavrov S. S. Concepte de bază și construcții ale limbajelor de programare. - Finanţe şi statistică, 1982. - 80 p.
  • Terence Pratt. Limbaje de programare: dezvoltare și implementare = Programming Language Design and Implementation (PLDI). — Ediția I. - Lumea , 1979.
  • Alfred Aho, Ravi Seti, Jeffrey Ullman. Compilatoare: principii, tehnologii și instrumente. - Compania de Editura Addison-Wesley, Editura Williams, 1985, 2001, 2003. - 768 p. - ISBN 5-8459-0189-8 (rusă), 0-201-10088-6 (original).
  • Cărți Time Life. Computer Language = Limbaje computerului. - M . : Mir, 1989. - T. 2. - 240 p. - (Înțelegerea computerelor). — 100.000 de exemplare.  — ISBN 5-03-001148-X .
  • Luca Cardelli . Programare tip( (engleză)) // Rapoarte de ultimă generație IFIP. - Springer-Verlag, 1991. -Vol. Descrierea formală a conceptelor de programare. -S. 431-507.
  • Alfred Aho, Jeffrey Ullman. Bazele Informaticii. — Computer Science Press, 1992.
  • Lawrence C. Paulson . ML pentru programatorul de lucru. — al 2-lea. - Cambridge, Marea Britanie: Cambridge University Press, 1996. - 492 p. -ISBN 0-521-57050-6(copertă cartonată), 0-521-56543-X (copertă moale).
  • John C. Reynolds. Teorii ale limbajelor de programare . - Cambridge University Press, 1998. - ISBN 978-0-521-59414-1 (copertă cartonată), 978-0-521-10697-9 (copertă).
  • Andrew W. Appel. Implementare modernă a compilatorului în ML (în C, în Java)  (neopr.) . - Cambridge, Marea Britanie: Cambridge University Press, 1998. - 538 p. - ISBN (ML) 0-521-58274-1 (copertă cartonată), 0-521-60764-7 (copertă cartonată).
  • Robert W. Sebesta. Concepte de bază ale limbajelor de programare \u003d Concepte ale limbajelor de programare / Per. din engleza. - a 5-a ed. - M. : Williams , 2001. - 672 p. - 5000 de exemplare.  — ISBN 5-8459-0192-8 (rusă), ISBN 0-201-75295-6 (engleză).
  • Wolfenhagen V. E. Proiectări de limbaje de programare. Metode de descriere. - M . : Centrul YurInfoR, 2001. - 276 p. — ISBN 5-89158-079-9 .
  • Parondzhanov V. D. Cum să îmbunătățiți activitatea minții. Algoritmi fără programatori - este foarte simplu! - M . : Delo, 2001. - 360 p. — ISBN 5-7749-0211-0 .
  • Pierce, Benjamin C. Tipuri și limbaje de programare . - MIT Press , 2002. - ISBN 0-262-16209-1 .
    • Traducere în rusă: Pierce B. Tipuri în limbaje de programare. - Dobrosvet , 2012. - 680 p. — ISBN 978-5-7913-0082-9 .
  • Terence Pratt, Marvin Zelkowitz. Limbaje de programare: dezvoltare și implementare. - editia a 4-a. - Peter, 2002. - (Classics of Computer Science). - ISBN 978-5-318-00189-5 .
  • John C. Mitchell Concepte în limbaje de programare. - Cambridge University Press, 2004. - ISBN 0-511-04091-1 (eBook în netLibrary); 0-521-78098-5 (copertă cartonată).
  • Peter Seibel . Codificatorii la serviciu. Reflecții despre profesia de programator. - Symbol-Plus, Sankt Petersburg. - 2011. - ISBN 978-5-93286-188-2 , 978-1-4302-1948-4 (engleză).

Link -uri