Tip sistem

Un sistem de tipuri  este un set de reguli în limbaje de programare care atribuie proprietăți, numite tipuri , diferitelor constructe care alcătuiesc un program  , cum ar fi variabile , expresii , funcții sau module . Rolul principal al sistemului de tip este de a reduce numărul de bug-uri din programe [1] prin definirea interfețelor între diferite părți ale programului și apoi verificarea consistenței interacțiunii acestor părți. Această verificare poate avea loc static ( în timpul compilării sau dinamic ( în timpul rulării ), sau o combinație a ambelor.

Definiție

Potrivit lui Pierce , sistemul de tipuri  este o metodă sintactică decidabilă de a demonstra absența anumitor comportamente de program prin clasificarea constructelor în funcție de tipurile de valori calculate [2] .

Descriere

Un exemplu de sistem de tip simplu poate fi văzut în limbajul C. Părțile unui program C sunt scrise ca definiții de funcție . Funcțiile se numesc între ele. Interfața unei funcții specifică numele funcției și o listă de valori care sunt transmise corpului acesteia. Funcția de apelare postulează numele funcției pe care dorește să o apeleze și numele variabilelor care dețin valorile pe care dorește să le transmită. În timpul execuției programului, valorile sunt plasate în stocare temporară, iar apoi execuția este trecută în corpul funcției apelate. Codul de funcție numit accesează și folosește valorile. Dacă instrucțiunile din corpul unei funcții sunt scrise cu presupunerea că o valoare întreagă ar trebui să le fie transmisă pentru procesare, dar codul de apel a trecut un număr în virgulă mobilă, atunci funcția apelată va evalua rezultatul greșit. Compilatorul C verifică tipurile definite pentru fiecare variabilă transmisă cu tipurile definite pentru fiecare variabilă în interfața funcției apelate. Dacă tipurile nu se potrivesc, compilatorul generează o eroare de compilare.

Din punct de vedere tehnic, sistemul de tip atribuie un tip fiecărei valori calculate și apoi, ținând evidența secvenței acelor calcule, încearcă să verifice sau să demonstreze că nu există erori de potrivire de tip . Sistemul de tip specific poate determina ce cauzează o eroare, dar de obicei scopul este de a preveni operațiunile care preiau anumite proprietăți ale parametrilor lor să primească parametri pentru care operațiunile respective nu au sens - prevenind erorile logice . În plus, previne și erorile la adresa de memorie .

Sistemele de tip sunt de obicei definite ca parte a limbajelor de programare și încorporate în interpreții și compilatoarele lor. Cu toate acestea, sistemul de tip al limbii poate fi extins cu instrumente speciale care efectuează verificări suplimentare bazate pe sintaxa de tastare inițială în limbaj.

Compilatorul poate folosi, de asemenea, un tip de valoare static pentru a optimiza stocarea și pentru a alege o implementare algoritmică a operațiilor pe acea valoare. De exemplu, în multe compilatoare C , un tip float este reprezentat de 32 de biți, conform specificației IEEE pentru operațiuni cu virgulă mobilă cu precizie unică . Pe baza acestui lucru, un set special de instrucțiuni pentru microprocesor va fi utilizat pentru valori de acest tip (adunare, înmulțire și alte operațiuni).

Numărul de restricții impuse tipurilor și modul în care acestea sunt calculate determină dactilografia limbii. În plus, în cazul polimorfismului de tip , limbajul poate specifica și pentru fiecare tip operația unor algoritmi specifici variați. Studiul sistemelor de tip este teoria tipurilor , deși în practică un anumit tip de sistem al unui limbaj de programare se bazează pe caracteristicile arhitecturii computerului, implementarea compilatorului și imaginea generală a limbajului.

Justificare formală

Justificarea formală a sistemelor de tip este teoria tipurilor . Un limbaj de programare include un sistem de tip pentru efectuarea verificării tipului în timpul compilării sau în timpul executării , necesitând declarații de tip explicite sau deducându -le de la sine. Mark Manasse a  formulat problema după cum urmează [3] :

Principala problemă pe care o rezolvă teoria tipurilor este să vă asigurați că programele au sens. Problema principală cu teoria tipurilor este că programele semnificative pot să nu se comporte așa cum s-au dorit. Consecința acestei tensiuni este căutarea unor sisteme de tip mai puternice.

Text original  (engleză)[ arataascunde] Problema fundamentală abordată de o teorie a tipurilor este să se asigure că programele au sens. Fundamentalul cauzat de o teorie a tipurilor este că programele semnificative nu pot avea semnificații problematice atribuite acestora. Căutarea unor sisteme de tip mai bogate rezultă din această tensiune. — Mark Massey [3]

Operația de atribuire a tipului, numită tastare, dă sens șirurilor de biți , cum ar fi o valoare din memoria computerului , sau obiectelor , cum ar fi o variabilă . Calculatorul nu are nicio modalitate de a distinge, de exemplu, o adresă din memorie de o instrucțiune de cod sau un caracter de un număr întreg sau cu virgulă mobilă , deoarece șirurile de biți care reprezintă aceste semnificații diferite nu au caracteristici evidente care să permită presupuneri cu privire la semnificația lor. Atribuirea de biți de tip șirurilor oferă această inteligibilitate, transformând astfel hardware -ul programabil într-un sistem de caractere format din acel hardware și software.

Verificarea coerenței tipului

Procesul de verificare și constrângere a tipului — verificarea tipului sau verificarea tipului — se poate face fie în timpul compilării tastare statică), fie în timpul execuției (tastare dinamică). Sunt posibile și soluții intermediare (cf. tastare secvențială ).

Dacă specificația limbajului impune ca regulile de tastare să fie strict implementate (adică să permită într-o măsură sau alta doar acele conversii automate de tip care nu pierd informații), un astfel de limbaj se numește strongly typed ;  ), altfel slab tipizat . Acești termeni sunt condiționati și nu sunt folosiți în justificări formale.

Verificarea tipului static

Verificarea dinamică a tipului și informațiile de tip

Tastare puternică și liberă

Tip siguranța și protecția adresei de memorie

Limbi tastate și netactilizate

Un limbaj se numește tipat dacă specificația fiecărei operații definește tipurile de date cărora li se poate aplica această operație, implicând inaplicabilitatea acesteia la alte tipuri [4] . De exemplu, datele pe care le reprezintă „ acest text citat ” sunt de tipul „ строка”. În majoritatea limbajelor de programare, împărțirea unui număr la un șir nu are sens, iar majoritatea limbajelor moderne vor respinge un program care încearcă să efectueze o astfel de operație. În unele limbi, o operațiune fără sens va fi detectată în timpul compilării ( static typing ) și respinsă de compilator. În altele, va fi detectat în timpul execuției ( tastare dinamică ), aruncând o excepție .

Un caz special de limbi tipizate sunt limbile cu un singur tip ( ing. limbaj cu un singur  tip ), adică limbile cu un singur tip. Acestea sunt, de obicei, limbaje de scriptare sau de marcare , cum ar fi REXX și SGML , al căror singur tip de date este șirul de caractere, folosit pentru a reprezenta atât caracterele, cât și datele numerice.

Limbile netipizate, spre deosebire de cele tipizate, vă permit să efectuați orice operație asupra oricăror date, care în ele sunt reprezentate prin lanțuri de biți de lungime arbitrară [4] . Majoritatea limbajelor de asamblare sunt netipizate . Exemple de limbaje netipizate de nivel înalt sunt BCPL , BLISS , Forth , Refal .

În practică, puține limbi pot fi considerate dactilografiate din punct de vedere al teoriei tipurilor (permițând sau respingând toate operațiunile), majoritatea limbilor moderne oferă doar un anumit grad de tipare [4] . Multe limbi industriale oferă capacitatea de a ocoli sau de a întrerupe sistemul de tipare, schimbând siguranța tipului pentru un control mai fin asupra execuției programului ( pune de cuvinte ).

Tipuri și polimorfism

Termenul „polimorfism” se referă la capacitatea codului de a rula pe valori de mai multe tipuri diferite sau la capacitatea diferitelor instanțe ale aceleiași structuri de date de a conține elemente de diferite tipuri. Unele sisteme de tip permit polimorfismului să îmbunătățească potențial reutilizarea codului : în limbajele cu polimorfism, programatorii trebuie să implementeze doar o singură dată structuri de date, cum ar fi o listă sau o matrice asociativă și nu au nevoie să dezvolte o implementare pentru fiecare tip de element pe care îl planifică. a depozita.structuri. Din acest motiv, unii informaticieni se referă uneori la utilizarea anumitor forme de polimorfism ca „ programare generică ”. Justificările pentru polimorfism din punctul de vedere al teoriei tipurilor sunt practic aceleași ca și pentru abstractizare , modularitate și, în unele cazuri , subtipări de date .

Duck typing

Sisteme de tip special

Au fost dezvoltate o serie de sisteme de tip special pentru utilizarea în anumite condiții cu anumite date, precum și pentru analiza statică a programelor. În cea mai mare parte, ele se bazează pe ideile teoriei formale a tipurilor și sunt utilizate doar ca parte a sistemelor de cercetare.

Tipuri existențiale

Tipurile existențiale, adică tipurile definite de un cuantificator existențial (cuantificator de existență) , sunt un mecanism de încapsulare la nivel de tip : este un tip compozit care ascunde implementarea unui tip în compoziția sa.

Conceptul de tip existențial este adesea folosit împreună cu conceptul de tip de înregistrare pentru a reprezenta module și tipuri de date abstracte , datorită scopului lor de a separa implementarea de interfață. De exemplu, un tip T = ∃X { a: X; f: (X → int); }descrie o interfață de modul (familii de module cu aceeași semnătură) care are o valoare de date de tip Xși o funcție care preia un parametru de exact același tip Xși returnează un număr întreg. Implementarea poate fi diferită:

Ambele tipuri sunt subtipuri de tip existențial mai general Tși corespund unor tipuri concret implementate, deci orice valoare care aparține unuia dintre ele aparține și tipului T. Dacă t este o valoare de tip T, atunci t.f(t.a)trece verificarea tipului, indiferent de tipul abstract căruia îi aparține X. Acest lucru oferă flexibilitate în alegerea tipurilor care sunt adecvate pentru o anumită implementare, deoarece utilizatorii externi accesează doar valorile tipului de interfață (existențial) și sunt izolați de aceste variații.

În general, verificatorul de consistență de tip nu poate determina cărui tip existențial îi aparține un anumit modul. În exemplul de mai sus intT { a: int; f: (int → int); }, ar putea avea și tipul ∃X { a: X; f: (int → int); }. Cea mai simplă soluție este să specificați în mod explicit pentru fiecare modul tipul implicit în acesta, de exemplu:

Deși tipurile și modulele de date abstracte au fost folosite în limbajele de programare de mult timp, un model formal de tipuri existențiale nu a fost construit până în 1988 [5] . Teoria este un calcul lambda de ordinul doi similar cu Sistemul F , dar cu cuantificare existențială în loc de cuantificare universală .

Tipurile existențiale sunt disponibile în mod explicit ca o extensie experimentală a limbajului Haskell , unde sunt o sintaxă specială care vă permite să utilizați o variabilă de tip într-o definiție de tip algebric fără a o muta în semnătura unui constructor de tip , adică fără a-i crește aritatea . 6] . Limbajul Java oferă o formă limitată de tipuri existențiale prin wildcard . În limbajele care implementează polimorfismul clasic în stil ML , tipurile existențiale pot fi simulate prin intermediul așa-numitelor „ valori indexate de tip ” [7] .

Atribuire explicită și implicită a tipului

Multe sisteme de tip static, cum ar fi cele din C și Java, necesită o declarație de tip : programatorul trebuie să atribuie în mod explicit un anumit tip fiecărei variabile. Alții, cum ar fi sistemul de tip Hindley-Milner utilizat în ML și Haskell , fac inferență de tip : compilatorul deduce tipurile de variabile pe baza modului în care programatorul folosește acele variabile.

De exemplu, pentru o funcție f(x,y)care efectuează adunarea xși y, compilatorul ar putea deduce că xși ytrebuie să fie numere - deoarece operația de adunare este definită numai pentru numere. Prin urmare, apelarea unei funcții undeva în program fpentru alți parametri decât numerici (de exemplu, pentru un șir sau o listă) semnalează o eroare.

Constantele și expresiile numerice și șir de caractere exprimă adesea un tip într-un anumit context. De exemplu, o expresie 3.14ar putea însemna un număr real , în timp ce [1,2,3]ar putea fi o listă de numere întregi - de obicei o matrice .

În general, inferența de tip este posibilă dacă este fundamental determinabilă în teoria tipurilor. Mai mult decât atât, chiar dacă inferența este indecidabilă pentru o anumită teorie a tipurilor, inferența este adesea posibilă pentru multe programe reale. Sistemul de tip Haskell , care este o variație a sistemului de tip Hindley-Milner , este o restricție a sistemului Fω la așa-numitele tipuri polimorfe de prim rang asupra cărora inferența este decidabilă. Multe compilatoare Haskell oferă polimorfism de rang arbitrar ca extensie, dar acest lucru face ca inferența de tip să fie indecidabilă, deci este necesară declararea explicită a tipului. Cu toate acestea, verificarea coerenței tipului rămâne decidabilă, iar pentru programele cu polimorfism de prim rang, tipurile sunt încă derivabile.

Sisteme de tip unificat

Unele limbaje, cum ar fi C# , au un sistem de tip unificat [8] . Aceasta înseamnă că toate tipurile de limbaj, până la cele primitive , sunt moștenite de la un singur obiect rădăcină (în cazul C#, din clasa Object). Java are mai multe tipuri primitive care nu sunt obiecte. Alături de acestea, Java oferă și tipuri de obiecte wrapper, astfel încât dezvoltatorul să poată utiliza tipurile primitive sau obiecte după cum consideră de cuviință.

Compatibilitate tip

Mecanismul de verificare a consistenței tipului într-un limbaj tipizat static verifică dacă orice expresie este conformă cu tipul așteptat de contextul în care apare. De exemplu, într-o instrucțiune de atribuire a tipului, tipul dedus pentru expresie trebuie să se potrivească cu tipul declarat sau dedus pentru variabila . Notația de conformitate, numită compatibilitate , este specifică fiecărei limbi. x := eex

Dacă eși xsunt de același tip și atribuirea este permisă pentru acel tip, atunci expresia este legală. Prin urmare, în cele mai simple sisteme de tip, problema compatibilității a două tipuri este simplificată la problema egalității lor ( echivalența ). Cu toate acestea, diferitele limbi au criterii diferite pentru a determina compatibilitatea tipului a două expresii. Aceste teorii ale echivalenței variază între două extreme: sisteme de tip structural  , în care două tipuri sunt echivalente dacă descriu aceeași structură internă a unei valori; și sisteme de tip nominativ , în care tipurile diferite din punct de vedere sintactic nu sunt niciodată echivalente ( adică două tipuri sunt egale numai dacă identificatorii lor sunt egali) . 

În limbile cu subtipuri , regulile de compatibilitate sunt mai complexe. De exemplu, dacă Aeste un subtip de B, atunci o valoare de tip Apoate fi utilizată într-un context care așteaptă o valoare de tip B, chiar dacă invers nu este adevărat. Ca și în cazul echivalenței, relațiile de subtip variază în funcție de limbă și sunt posibile multe variații ale regulilor. Prezența polimorfismului parametric sau situațional într-o limbă poate afecta și compatibilitatea de tip.

Influența asupra stilului de programare

Unii programatori preferă sistemele de tip static , alții preferă sistemele dinamice . Limbile tipizate static semnalează erori de consistență de tip în timpul compilării , pot genera cod executabil mai eficient, iar pentru astfel de limbi, este fezabilă o completare mai relevantă în IDE-uri . Susținătorii tastării dinamice susțin că acestea sunt mai potrivite pentru prototiparea rapidă și că erorile de potrivire a tipurilor sunt doar o mică parte din erorile potențiale din programe [9] [10] . Pe de altă parte, în limbile tipizate static, declararea explicită a tipului nu este, de obicei, necesară dacă limbajul acceptă inferența tipului , iar unele limbi tipizate dinamic efectuează optimizări ale timpului de rulare [11] [12] , adesea prin utilizarea tipului parțial deducere [13] .

Vezi și

Note

  1. Cardelli, 2004 , p. unu.
  2. Pierce, 2002 , p. unu.
  3. 12 Pierce , 2002 , p. 208.
  4. 1 2 3 Andrew Cooke. Introducere în limbajele computerului (link indisponibil) . Preluat la 13 iulie 2012. Arhivat din original la 15 august 2012. 
  5. Mitchell, Plotkin, 1988 .
  6. Tipuri existențiale pe HaskellWiki . Consultat la 15 iulie 2014. Arhivat din original la 20 iulie 2014.
  7. Valori indexate tip . Consultat la 15 iulie 2014. Arhivat din original la 21 aprilie 2016.
  8. Standard ECMA-334 Arhivat la 31 octombrie 2010 la Wayback Machine , 8.2.4 Unificarea sistemului de tip.
  9. Meijer, Drayton .
  10. Amanda Laucher, Paul Snively. Tipuri vs  teste . InfoQ. Consultat la 26 februarie 2014. Arhivat din original pe 2 martie 2014.
  11. Adobe și Mozilla Foundation la Open Source Flash Player Scripting Engine . Consultat la 26 februarie 2014. Arhivat din original la 21 octombrie 2010.
  12. Psyco, un compilator specializat în Python . Preluat la 26 februarie 2014. Arhivat din original la 29 noiembrie 2019.
  13. C-Extensions pentru Python Arhivat la 11 august 2007 la Wayback Machine . Cython (11.05.2013). Preluat pe 2013-07-17

Literatură

Link -uri