Mokomoji programa rašyti programavimo kalbomis. Pagrindiniai programavimo principai: statinis ir dinaminis spausdinimas Stiprus ir silpnas spausdinimas

OO metodo spausdinimo paprastumas yra objekto skaičiavimo modelio paprastumo pasekmė. Praleidę detales, galime pasakyti, kad vykdant objektinę sistemą įvyksta tik vieno tipo įvykis - funkcijos iškvietimas:


reiškiantis atlikti operaciją f virš pritvirtinto objekto x, praeinant argumentams arg(galbūt keli argumentai arba nė vieno). „Smalltalk“ programuotojai šiuo atveju kalba apie „objekto praleidimą“. xžinutes f su argumentu arg“, tačiau tai tik terminijos skirtumas, todėl jis yra nereikšmingas.

Tai, kad viskas remiasi šia pagrindine konstrukcija, paaiškina dalį OO idėjų grožio jausmo.

Iš pagrindinio dizaino sekite tas neįprastas situacijas, kurios gali kilti vykdymo metu:

Apibrėžimas: tipo pažeidimas

Vykdymo laiko tipo pažeidimas arba sutrumpintai tipo pažeidimas įvyksta skambučio metu x.f(arg), Kur x pritvirtintas prie objekto O.B.J., jei kuris nors:

[x]. nėra atitinkamo komponento f ir taikomas O.B.J.,

[x]. toks komponentas egzistuoja, tačiau argumentas arg jam nepriimtina.

Įvedant tekstą reikia vengti tokių situacijų:

OO sistemų spausdinimo problema

Kada aptinkame, kad tipo pažeidimas gali įvykti vykdant objektinę sistemą?

Pagrindinis žodis yra Kada. Anksčiau ar vėliau suprasite, kad yra tipo pažeidimas. Pavyzdžiui, bandymas paleisti Launch Torpedo komponentą objekte Darbuotojas neveiks ir nepavyks. Tačiau galbūt norėsite klaidas rasti anksčiau nei vėliau.

Statinis ir dinaminis spausdinimas

Nors galimi tarpiniai variantai, čia yra du pagrindiniai metodai:

[x]. Dinaminis spausdinimas: palaukite, kol baigsis kiekvienas skambutis, tada priimkite sprendimą.

[x]. Statinis spausdinimas: atsižvelgiant į taisyklių rinkinį, iš šaltinio teksto nustatykite, ar vykdant galimi tipo pažeidimai. Sistema veikia, jei taisyklės garantuoja, kad klaidų nebus.

Šiuos terminus lengva paaiškinti: naudojant dinaminį spausdinimą tipo tikrinimas vyksta sistemai veikiant (dinamiškai), o naudojant statinį teksto tipo tikrinimas atliekamas statiškai (prieš vykdymą).

Statinis spausdinimas daro prielaidą automatinis patikrinimas, paprastai priskiriamas kompiliatoriui. Dėl to turime paprastą apibrėžimą:

Apibrėžimas: statiškai spausdinama kalba

Į objektą orientuota kalba įvedama statiškai, jei ji pateikiama su nuoseklių, kompiliatoriaus patikrintų taisyklių rinkiniu, užtikrinančiu, kad sistemos vykdymas nesukels tipo pažeidimų.

Terminas " stiprus rašyti" ( stiprus). Tai atitinka apibrėžimo ultimatumą, reikalaujantį visiško tipo pažeidimo nebuvimo. Taip pat galima silpnas (silpnas) statinio spausdinimo formos, kai taisyklės pašalina tam tikrus pažeidimus, nepašalindami jų visiškai. Šia prasme kai kurios į objektus orientuotos kalbos yra statiškai silpnai įvestos. Kovosime už stipriausią tipizaciją.

Dinamiškai įvestos kalbos, žinomos kaip neįvestos kalbos, neturi tipo deklaracijų, o vykdymo metu subjektai gali turėti bet kokias reikšmes. Statinis tipo tikrinimas juose negalimas.

Rašymo taisyklės

Mūsų OO žymėjimas yra statiškai įvestas. Jo tipo taisyklės buvo pristatytos ankstesnėse paskaitose ir susideda iš trijų paprastų reikalavimų.

[x]. Deklaruojant kiekvieną objektą ar funkciją, turi būti nurodytas jo tipas, pvz. acc: ACCOUNT. Kiekviena rutina turi 0 ar daugiau formalių argumentų, kurių tipas turi būti nurodytas, pavyzdžiui: įdėti(x:G;i:INTEGER).

[x]. Bet kokioje užduotyje x:=y ir už bet kokį iškvietimą į paprogramę, kurioje y yra tikrasis formalaus argumento argumentas x, šaltinio tipas y turi būti suderinamas su tikslinio tipo x. Suderinamumo apibrėžimas pagrįstas paveldėjimu: B suderinama su A, jei tai jos palikuonis, papildytas bendrųjų parametrų taisyklėmis (žr. 14 paskaitą).

[x]. Skambinti x.f(arg) to reikalauja f buvo pagrindinės klasės komponentas tiksliniam tipui x, Ir f turi būti eksportuojami į klasę, kurioje pasirodo skambutis (žr. 14.3).

Realizmas

Nors statiškai spausdinamos kalbos apibrėžimas pateiktas gana tiksliai, jo neužtenka – kuriant spausdinimo taisykles reikalingi neformalūs kriterijai. Panagrinėkime du kraštutinius atvejus.

[x]. Visiškai teisinga kalba, kurioje kiekviena sintaksiškai teisinga sistema taip pat yra teisinga. Tipo deklaravimo taisyklių nereikia. Tokios kalbos egzistuoja (įsivaizduokite lenkišką užrašą sveikiesiems skaičiams pridėti ir atimti). Deja, jokia tikroji universali kalba neatitinka šio kriterijaus.

[x]. Visiškai neteisinga kalba, kurią lengva sukurti naudojant bet kurią esamą kalbą ir pridedant spausdinimo taisyklę, kuri leidžia bet koks sistema neteisinga. Pagal apibrėžimą kalba yra spausdinama: kadangi nėra sistemų, kurios laikytųsi taisyklių, jokia sistema nesukels tipo pažeidimo.

Galime sakyti, kad pirmojo tipo kalbos tinkamas, Bet nenaudingas, pastarasis gali būti naudingas, bet netinkamas.

Praktiškai mums reikia tipinės sistemos, kuri būtų tinkama naudoti ir naudinga: pakankamai galinga, kad atitiktų skaičiavimo poreikius, ir pakankamai patogi, kad nesukeltų mūsų sunkumų, susijusių su spausdinimo taisyklėmis.

Tarkim ta kalba tikroviškas, jei jis yra tinkamas naudoti ir naudingas praktikoje. Priešingai nei statinio spausdinimo apibrėžimas, kuris suteikia kategorišką atsakymą į klausimą: " Ar kalba X įvedama statiškai?“, realizmo apibrėžimas iš dalies yra subjektyvus.

Šioje paskaitoje įsitikinsime, kad mūsų siūloma žyma yra tikroviška.

Pesimizmas

Statinis spausdinimas pagal savo pobūdį lemia „pesimistinę“ politiką. Bandymas tai užtikrinti visi skaičiavimai nesukelia nesėkmių, atmeta skaičiavimai gali baigtis be klaidų.

Apsvarstykite įprastą, neobjektyvią, į Paskalį panašią kalbą su įvairiais tipais TIKRAS Ir SVEIKI SKAIČIUS. Kai aprašoma n:INTEGER; r: Tikras operatorius n:=r bus atmesti kaip pažeidžiantys taisykles. Taigi, kompiliatorius atmes visus šiuos teiginius:


Jei leisime juos vykdyti, pamatysime, kad [A] veiks visada, nes bet kuri skaičių sistema tiksliai atvaizduoja tikrąjį skaičių 0,0, kuris vienareikšmiškai paverčiamas 0 sveikųjų skaičių. [B] taip pat beveik neabejotinai veiks. Veiksmo [C] rezultatas nėra akivaizdus (ar norime gauti rezultatą suapvalindami ar atmesdami trupmeninę dalį?). [D] atliks darbą, kaip ir operatorius:


jei n^2< 0 then n:= 3.67 end [E]

kuri apima nepasiekiamą užduotį ( n^2 yra skaičiaus kvadratas n). Po pakeitimo n^2įjungta n Tik važiavimų serija duos teisingą rezultatą. Užduotis n didelė tikroji vertybė, kurios negalima pavaizduoti kaip visumos, sukels nesėkmę.

Atspausdintomis kalbomis visi šie pavyzdžiai (veikiantys, neveikiantys, kartais veikiantys) negailestingai traktuojami kaip tipo aprašymo taisyklių pažeidimai ir bet kurio kompiliatoriaus atmetami.

Klausimas ne mes ar mes pesimistai, bet faktas yra kiek galime sau leisti būti pesimistai. Grįžtant prie realizmo reikalavimo: jei tipo taisyklės yra tokios pesimistinės, kad trukdo rašyti skaičiavimus, mes jas atmesime. Bet jei tipo saugumas pasiekiamas už nedidelę išraiškos galią, mes juos priimsime. Pavyzdžiui, kūrimo aplinkoje, kurioje teikiamos apvalinimo ir paryškinimo funkcijos - apvalus Ir sutrumpinti, operatorius n:=r yra laikomas neteisingu, teisingai, nes jis verčia jus aiškiai išrašyti konversiją iš tikrojo į sveikąjį skaičių, o ne naudoti numatytąsias dviprasmiškas konversijas.

Statinis spausdinimas: kaip ir kodėl

Nors statinio spausdinimo pranašumai yra akivaizdūs, verta apie juos pakalbėti dar kartą.

Privalumai

Statinio spausdinimo objektų technologijoje naudojimo priežastis išvardijome paskaitos pradžioje. Tai yra patikimumas, supratimo paprastumas ir efektyvumas.

Patikimumas atsiranda dėl klaidų, kurios kitu atveju galėtų pasireikšti tik eksploatacijos metu, ir tik kai kuriais atvejais. Pirmoji iš taisyklių, kuri verčia deklaruoti objektus, taip pat funkcijas, įveda programos teksto pertekliškumą, o tai leidžia kompiliatoriui, naudojant kitas dvi taisykles, aptikti neatitikimus tarp numatyto ir faktinio objektų, komponentų ir komponentų naudojimo. posakius.

Ankstyvas klaidų aptikimas taip pat svarbus, nes kuo ilgiau laukiame, kol jas surasime, tuo didės taisymo išlaidos. Šią savybę, intuityviai suprantamą visiems profesionaliems programuotojams, kiekybiškai patvirtina gerai žinomi Boehm darbai. Koregavimo sąnaudų priklausomybė nuo klaidų nustatymo laiko parodyta diagramoje, pagrįstoje daugelio didelių pramoninių projektų ir eksperimentų, atliktų su nedideliu kontroliuojamu projektu, duomenimis:

Ryžiai. 17.1. Lyginamosios klaidų taisymo išlaidos (paskelbta su leidimu)

Skaitomumas arba Lengva suprasti(skaitomumas) turi savo privalumų. Visuose šios knygos pavyzdžiuose tipo atsiradimas objekte suteikia skaitytojui informacijos apie jo paskirtį. Priežiūros etapo metu itin svarbu skaitomumas.

Pagaliau, efektyvumą gali nustatyti objektų technologijos sėkmę ar nesėkmę praktikoje. Jei nėra statinio spausdinimo vykdymui x.f(arg) tai gali užtrukti bet kiek laiko. Priežastis ta, kad vykdymo metu, nerandant f bazinėje tikslinėje klasėje x, paieškos bus tęsiamos su jos palikuonimis, ir tai yra teisingas kelias į neefektyvumą. Galite palengvinti problemą pagerindami komponento paiešką hierarchijoje. Dirigavo „Savęs kalbos“ autoriai puikus darbas, kuria siekiama sukurti geresnį kodą dinamiškai spausdinamai kalbai. Tačiau būtent statinis spausdinimas leido tokiam OO produktui priartėti arba prilygti tradicinei programinei įrangai.

Statinio spausdinimo raktas yra jau išsakyta mintis, kad kompiliatorius, generuojantis konstrukcijos kodą x.f(arg), žino tipą x. Dėl polimorfizmo neįmanoma vienareikšmiškai nustatyti tinkamos komponento versijos f. Tačiau deklaracija susiaurina galimų tipų rinkinį, leidžiantį kompiliatoriui sukurti lentelę, kuri suteikia prieigą prie tinkamo f su minimaliomis išlaidomis, - su ribota konstanta prieigos sunkumas. Atlikti papildomi optimizavimai statinis įrišimas Ir įdėklas- taip pat palengvina statinį spausdinimą, visiškai pašalinant pridėtines išlaidas.

Dinaminio spausdinimo atvejis

Nepaisant viso to, dinaminis spausdinimas nepraranda savo gerbėjų, ypač tarp „Smalltalk“ programuotojų. Jų argumentai pirmiausia pagrįsti aukščiau aptartu realizmu. Jie mano, kad statinis spausdinimas yra per daug ribojantis, neleidžiantis jiems laisvai reikšti savo kūrybinių idėjų, kartais tai vadina „skaistybės diržu“.

Galima sutikti su tokiais samprotavimais, tačiau tik statiškai įvestoms kalboms, kurios nepalaiko daugelio funkcijų. Verta paminėti, kad visos sąvokos, susijusios su tipo sąvoka ir pristatytos ankstesnėse paskaitose, yra būtinos - bet kurios iš jų atmetimas yra kupinas rimtų apribojimų, o jų įvedimas, priešingai, suteikia mūsų veiksmams lankstumo ir suteikia mums galimybė visiškai mėgautis statinio spausdinimo praktiškumu.

Tipavimas: sėkmės komponentai

Kokie yra tikroviško statinio spausdinimo mechanizmai? Visi jie buvo pristatyti ankstesnėse paskaitose, todėl galime tik trumpai prisiminti. Jų išvardijimas kartu parodo jų asociacijos nuoseklumą ir galią.

Mūsų tipo sistema yra visiškai pagrįsta koncepcija klasė. Net tokie pagrindiniai tipai kaip SVEIKI SKAIČIUS, todėl mums nereikia specialių taisyklių iš anksto apibrėžtiems tipams apibūdinti. (Čia mūsų žymėjimas skiriasi nuo „hibridinių“ kalbų, tokių kaip „Object Pascal“, „Java“ ir „C++“, kurios sujungia senesnių kalbų tipų sistemas su klasės objektų technologija.)

Išplėsti tipai Suteikite mums daugiau lankstumo leisdami tipus, kurių reikšmės žymi objektus, ir tipus, kurių reikšmės žymi nuorodas.

Lemiamas žodis kuriant lanksčią tipo sistemą priklauso paveldėjimo ir susijusi sąvoka suderinamumas. Taip įveikiamas pagrindinis klasikinių kalbų, pavyzdžiui, Pascal ir Ada, apribojimas, kuriose operatorius x:=y reikalauja, kad tipas x Ir y buvo tas pats. Ši taisyklė per griežta: ji draudžia naudoti objektus, galinčius žymėti susijusių tipų objektus ( TAUPOMOJI SĄSKAITA Ir CHECKING_ACCOUNT). Paveldėdami reikalaujame tik tipo suderinamumo y su tipu x, Pavyzdžiui, x turi tipą PASKYRA, y- TAUPOMOJI SĄSKAITA, o antroji klasė yra pirmosios įpėdinis.

Praktiškai statiškai įvestą kalbą reikia palaikyti daugybinis paveldėjimas. Yra gerai žinomi esminiai kaltinimai statiniam spausdinimui, kad jis neleidžia objektų interpretuoti kitaip. Taip, objektas DOKUMENTAS(dokumentas) gali būti perduodamas tinklu, todėl reikia komponentų, susijusių su tipu PRANEŠIMAS(pranešimas). Tačiau ši kritika galioja tik kalboms, kurios apsiriboja vienu paveldėjimu.

Ryžiai. 17.2. Daugialypis paveldėjimas

Universalumas reikalingi, pavyzdžiui, norint apibūdinti lanksčias, bet saugias konteinerio duomenų struktūras (pvz klasės SĄRAŠAS [G] ...). Be šio mechanizmo statiniam spausdinimui reikėtų deklaruoti skirtingas sąrašų klases, kurios skiriasi elementų tipu.

Kai kuriais atvejais reikalingas universalumas riba, kuri leidžia naudoti operacijas, taikomas tik bendrojo tipo objektams. Jei bendroji klasė SORTABLE_LIST palaiko rūšiavimą, tam reikia tipo objektų G, Kur G- bendrasis parametras, palyginimo operacijos buvimas. Tai pasiekiama sujungiant su G klasė, nurodanti bendrąjį apribojimą - PALYGINAMAS:


klasė SORTABLE_LIST...

Bet koks faktinis bendras parametras SORTABLE_LIST turi būti klasės palikuonis PALYGINAMAS, turintis reikiamą komponentą.

Kitas privalomas mechanizmas yra paskyrimo bandymas- organizuoja prieigą prie tų objektų, kurių tipo programinė įranga nekontroliuoja. Jeigu y yra duomenų bazės objektas arba objektas, gautas per tinklą, tada operatorius x ?= y pasisavins x prasmė y, Jei y yra suderinamo tipo arba, jei ne, duos x prasmė Tuštuma.

pareiškimai Susieję, kaip dizaino pagal sutartį idėjos dalį, su klasėmis ir jų komponentais išankstinių sąlygų, posąlygų ir klasių invariantų pavidalu, leidžia apibūdinti semantinius apribojimus, kurių neapima tipo specifikacija. Tokios kalbos kaip Pascal ir Ada turi diapazono tipus, kurie gali apriboti objektų reikšmes iki, tarkime, nuo 10 iki 20, bet jūs negalėsite jų naudoti norėdami užtikrinti, kad vertė i buvo neigiamas, visada dvigubai didesnis j. Į pagalbą ateina klasės invariantai, sukurti taip, kad tiksliai atspindėtų taikomus apribojimus, kad ir kokie sudėtingi jie būtų.

Prisegti skelbimai būtinos, kad praktiškai išvengtų kodo dubliavimo lavinų. Skelbdamas y: kaip x, jūs gaunate garantiją y pasikeis po bet kokių pakartotinių tipo deklaracijų x iš palikuonio. Be šio mechanizmo kūrėjai nuolat iš naujo deklaruotų, bandydami išlaikyti skirtingų tipų nuoseklumą.

Lipnios deklaracijos yra ypatingas paskutinio mums reikalingo kalbos mechanizmo atvejis – kovariacija, kurį išsamiai aptarsime vėliau.

Vystymosi metu programinės įrangos sistemos Tiesą sakant, būtina dar viena nuosavybė, būdinga pačiai plėtros aplinkai - greitas laipsniškas perkompiliavimas. Kai rašote arba modifikuojate sistemą, norite kuo greičiau pamatyti pakeitimų poveikį. Naudodami statinį spausdinimą, turite duoti kompiliatoriui laiko dar kartą patikrinti tipus. Dėl tradicinių kompiliavimo veiksmų reikia iš naujo išversti visą sistemą (ir jos asamblėjos), ir šis procesas gali būti skausmingai ilgas, ypač pereinant prie didesnio masto sistemų. Šis reiškinys tapo argumentu už interpretacinis sistemos, pvz., ankstyvosios Lisp arba Smalltalk aplinkos, paleido sistemą be jokio apdorojimo ir be tipo tikrinimo. Šis argumentas dabar pamirštas. Geras šiuolaikinis kompiliatorius nustato, kaip pasikeitė kodas nuo paskutinio kompiliavimo, ir apdoroja tik tuos pakeitimus, kuriuos rado.

„Ar kūdikis spausdinamas“?

Mūsų tikslas - griežtas statinis spausdinimas. Štai kodėl turime vengti bet kokių spragų mūsų „žaidime pagal taisykles“, bent jau tiksliai jas identifikuoti, jei tokių yra.

Dažniausia statinio tipo kalbų spraga yra konversijų, kurios keičia objekto tipą, buvimas. C ir jo išvestinėse kalbose jie vadinami „tipo liejimu“ arba liejimu. Įrašas (OTHER_TYPE)x rodo, kad vertė x kompiliatorius suvokia kaip turintį tipą OTHER_TYPE, atsižvelgiant į tam tikrus galimų tipų apribojimus.

Tokie mechanizmai apeina tipo tikrinimo apribojimus. Liejimas yra įprastas programuojant C, įskaitant ANSI C dialektą. Net C++ kalboje, tipo liejimas, nors ir rečiau, išlieka įprastas ir galbūt būtinas.

Laikytis statinio spausdinimo taisyklių nėra taip paprasta, jei jas bet kada galima apeiti liejant.

Rašymas ir susiejimas

Nors kaip šios knygos skaitytojas tikriausiai atskirsite statinį spausdinimą nuo statinio privalomas, yra žmonių, kurie to negali padaryti. Tai iš dalies gali būti dėl Smalltalk įtakos, kuri palaiko dinamišką požiūrį į abi problemas ir gali sukelti klaidingą nuomonę, kad jos turi tą patį sprendimą. (Savo knygoje teigiame, kad norint sukurti patikimas ir lanksčias programas, pageidautina derinti statinį spausdinimą ir dinaminį įrišimą.)

Tiek spausdinimas, tiek įrišimas susijęs su pagrindinės konstrukcijos semantika x.f(arg), bet atsakykite į du skirtingus klausimus:

Rašymas ir susiejimas

[x]. Rašymo klausimas: kai turime tiksliai žinoti, kad operacija, atitinkanti f, taikomas prie objekto prijungtam objektui x(su parametru arg)?

[x]. Susiejimo klausimas: Kada turėtume žinoti, kokią operaciją inicijuoja duotas skambutis?

Įvedant tekstą atsakoma į prieinamumo klausimą mažiausiai vienas operacijas, už atranką atsakingas privalomas būtina.

Objekto požiūriu:

[x]. problema, kuri kyla spausdinant, yra susijusi su polimorfizmas: nes xvykdymo metu gali atstovauti kelių skirtingų tipų objektus, turime būti tikri, kad atstovaujanti operacija f, prieinama kiekvienu iš šių atvejų;

[x]. sukelta įrišimo problema pakartotiniai pranešimai: Kadangi klasė gali pakeisti paveldėtus komponentus, gali būti dvi ar daugiau operacijų, kurios teigia atstovaujančios fšiame kvietime.

Abi problemos gali būti išspręstos tiek dinamiškai, tiek statiškai. Esamos kalbos pateikia visus keturis sprendimus.

[x]. Kai kurios neobjektinės kalbos, tarkime, Pascalis ir Ada, įgyvendina ir statinį spausdinimą, ir statinį įrišimą. Kiekvienas objektas atstovauja tik vieno tipo objektus, apibrėžtus statiškai. Tai užtikrina sprendimo patikimumą, kurio kaina yra jo lankstumas.

[x].„Smalltalk“ ir kitose į objektus orientuotose kalbose yra dinaminio įrišimo ir dinaminio spausdinimo funkcijų. Šiuo atveju pirmenybė teikiama lankstumui kalbos patikimumo sąskaita.

[x]. Kai kurios neobjektinės kalbos palaiko dinaminį spausdinimą ir statinį susiejimą. Tarp jų yra surinkimo kalbos ir daugybė scenarijų kalbų.

[x]. Statinio spausdinimo ir dinaminio įrišimo idėjos įkūnytos šioje knygoje siūlomoje žymėjime.

Atkreipkime dėmesį į C++ kalbos, kuri palaiko statinį spausdinimą, originalumą, nors ir nėra griežta dėl tipo liejimo, statinio įrišimo (pagal numatytuosius nustatymus), dinaminio įrišimo su aiškia virtualaus ( virtualus) skelbimai.

Statinio spausdinimo ir dinaminio įrišimo pasirinkimo priežastis yra akivaizdi. Pirmas klausimas yra toks: „Kada sužinosime, kad komponentai egzistuoja? - siūlo statinį atsakymą: " Kuo anksčiau, tuo geriau", o tai reiškia: kompiliavimo metu. Antrasis klausimas: "Kokį komponentą turėčiau naudoti?" reikia dinamiško atsakymo: " ta, kurios tau reikia", - atitinka dinaminį objekto tipą, nustatytą vykdymo metu. Tai vienintelis priimtinas sprendimas, jei statinis ir dinaminis susiejimas duoda skirtingus rezultatus.

Šis paveldėjimo hierarchijos pavyzdys padės paaiškinti šias sąvokas:

Ryžiai. 17.3. Lėktuvų tipai

Apsvarstykite iššūkį:


my_aircraft.lower_landing_gear

Įvedimo klausimas: kada įsitikinti, kad čia bus komponentas žemesnė_važiuoklė(„žemesnė važiuoklė“), taikoma objektui (skirta KOPTERIS jo iš viso nebus) Klausimas apie įrišimą: kurį iš kelių galimų variantų pasirinkti.

Statinis susiejimas reikštų, kad nepaisome pridedamo objekto tipo ir pasitikime subjekto deklaracija. Dėl to, kalbant apie Boeing 747-400, mes ragintume versiją, skirtą įprastiems 747 serijos lėktuvams, o ne jų 747-400 modifikacijai. Dinaminis susiejimas pritaiko objektui reikalingą operaciją, ir tai yra teisingas metodas.

Naudojant statinį spausdinimą, kompiliatorius neatmes iškvietimo, jei galima garantuoti, kad kai programa vykdoma prieš objektą mano_lėktuvas objektas, tiekiamas su atitinkamu komponentu žemesnė_važiuoklė. Pagrindinė garantijų gavimo technika yra paprasta: privalomai paskelbus mano_lėktuvasį jo tipo bazinę klasę būtina įtraukti tokį komponentą. Štai kodėl mano_lėktuvas negali būti deklaruojamas kaip LĖKVIAI, nes pastarasis neturi žemesnė_važiuoklėšiame lygyje; sraigtasparniai, bent jau mūsų pavyzdyje, nemoka nuleisti važiuoklės. Jei subjektą paskelbsime kaip LĖKTUVA, - klasė, kurioje yra reikalingas komponentas - viskas bus gerai.

Dinaminiam rašymui Smalltalk stiliumi reikia palaukti skambučio ir jo vykdymo metu patikrinti, ar yra norimas komponentas. Toks elgesys įmanomas prototipams ir eksperimentinėms kūrimui, tačiau nepriimtinas pramoninėms sistemoms – skrydžio metu per vėlu paklausti, ar turite važiuoklę.

Kovariacija ir vaiko slėpimas

Jei pasaulis būtų paprastas, pokalbis apie spausdinimą galėtų būti baigtas. Nustatėme statinio spausdinimo tikslus ir naudą, išnagrinėjome apribojimus, kuriuos turi atitikti tikroviškos tipo sistemos, ir patikrinome, ar siūlomi tipavimo metodai atitinka mūsų kriterijus.

Tačiau pasaulis nėra paprastas. Derinant statinį spausdinimą su kai kuriais programinės įrangos inžinerijos reikalavimais, kyla problemų, kurios yra sudėtingesnės, nei atrodo iš pirmo žvilgsnio. Du mechanizmai sukelia problemų: kovariacija- pakeisti parametrų tipus iš naujo apibrėžiant, palikuonių slėptuvė- palikuonių klasės galimybė apriboti paveldėtų komponentų eksporto statusą.

Kovariacija

Kas nutinka komponento argumentams, kai jo tipas yra nepaisomas? Tai yra didelė problema, ir mes jau matėme daugybę jos pasireiškimo pavyzdžių: įrenginiai ir spausdintuvai, pavieniai ir dvigubai susieti sąrašai ir kt. (žr. 16.6, 16.7 skyrius).

Štai dar vienas pavyzdys, padedantis išsiaiškinti problemos pobūdį. Ir nors tai toli nuo tikrovės ir metaforiška, jo artumas programinės įrangos schemoms akivaizdus. Be to, ją analizuodami dažnai grįšime prie problemų iš praktikos.

Įsivaizduokime čempionatui besiruošiančią universiteto slidinėjimo komandą. Klasė MERGAITĖ apima slidininkus, rungtyniaujančius moterų komandoje, BERNIUKAS- slidininkai. Nemažai dalyvių iš abiejų komandų reitinguojami, parodžiusi gerus rezultatus ankstesnėse varžybose. Tai jiems svarbu, nes dabar jie bėgs pirmi, įgydami pranašumą prieš kitus. (Ši taisyklė, suteikianti privilegijų jau turintiems privilegijuotiems žmonėms, galbūt ir daro slalomą ir lygumų slidinėjimą tokiu patraukliu daugelio žmonių akimis, nes tai yra gera paties gyvenimo metafora.) Taigi turime dvi naujas klases: RANKED_GIRL Ir RANKED_BOY.

Ryžiai. 17.4. Slidininkų klasifikacija

Sportininkams nakvynei rezervuota nemažai kambarių: tik vyrams, tik merginoms, tik prizininkėms. Norėdami tai parodyti, naudojame lygiagrečią klasių hierarchiją: KAMBARYS, GIRL_ROOM Ir RANKED_GIRL_ROOM.

Čia yra klasės eskizas SLIDININKAS:


- Sugyventinis.
...Kiti galimi komponentai praleisti šioje ir tolesnėse klasėse...

Mus domina du komponentai: atributas sugyventinis ir procedūra Dalintis, kuris „įdeda“ šį slidininką į tą patį kambarį su dabartiniu slidininku:


Deklaruojant subjektą kitas galite atsisakyti tipo SLIDININKAS fiksuoto tipo naudai kaip sugyventinis(arba kaip Dabartinė Dėl sugyventinis Ir kitas tuo pačiu metu). Tačiau trumpam pamirškime tipų priskyrimą (prie jų grįšime vėliau) ir pažvelkime į kovariacijos problemą pradine forma.

Kaip įvesti tipo nepaisymą? Taisyklėse reikalaujama, kad berniukai ir merginos, nugalėtojai ir kiti dalyviai būtų apgyvendinti atskirai. Norėdami išspręsti šią problemą, nepaisydami pakeiskime komponento tipą sugyventinis, kaip parodyta toliau (čia ir žemiau nepaisyti elementai yra pabraukti).


- Sugyventinis.

Atitinkamai iš naujo apibrėžkime procedūros argumentą Dalintis. Išsamesnė klasės versija dabar atrodo taip:


- Sugyventinis.
-- Pasirinkite kitą kaip kaimyną.

Panašiai turėtumėte pakeisti visus sugeneruotus iš SLIDININKAS klases (šiuo metu nenaudojame tipo įrišimo). Dėl to turime tokią hierarchiją:

Ryžiai. 17.5. Dalyvių hierarchija ir pakartotiniai apibrėžimai

Kadangi paveldėjimas yra specializacija, tipo taisyklės reikalauja, kad šiuo atveju būtų nepaisoma komponento rezultato sugyventinis, naujasis tipas buvo pradinio palikuonis. Tas pats pasakytina ir apie argumento tipo nepaisymą kitas paprogramės Dalintis. Ši strategija, kaip žinome, vadinama kovariacija, kur priešdėlis „co“ rodo bendrą parametro tipų ir rezultato pasikeitimą. Priešinga strategija vadinama prieštaringumas.

Visi mūsų pavyzdžiai įtikina praktinį kovariacijos poreikį.

[x]. Atskirai susietas sąrašo elementas SUSIJUSI turi būti susietas su kitu panašiu elementu ir pavyzdžiu BI_LINKABLE- su tokiu kaip jis pats. Kovariantą reikės nepaisyti ir argumentą įtraukti įdėti_į dešinę.

[x]. Bet kokia paprogramė LINKED_LIST su argumentu kaip SUSIJUSI persikėlus į TWO_WAY_LIST reikės argumentų BI_LINKABLE.

[x]. Procedūra set_alternate priima PRIETAISAS- argumentai klasėje PRIETAISAS Ir SPAUSDINtuvas-argumentas - klasėje SPAUSDINtuvas.

Kovariantų perrašymas tapo ypač plačiai paplitęs, nes informacijos slėpimas lemia formos procedūrų kūrimą


-- Nustatyti atributą į v.

dirbti su attrib tipo SOME_TYPE. Tokios procedūros yra natūraliai kovariacinės, nes bet kuri klasė, kuri pakeičia atributo tipą, turi atitinkamai iš naujo apibrėžti argumentą set_attrib. Nors pateikti pavyzdžiai telpa į vieną schemą, kovariacija yra daug plačiau paplitusi. Pagalvokite, pavyzdžiui, apie procedūrą ar funkciją, kuri atlieka atskirai susietų sąrašų sujungimą ( LINKED_LIST). Jo argumentas turi būti iš naujo apibrėžtas kaip dvigubai susietas sąrašas ( TWO_WAY_LIST). Universalus papildymo veikimas infix "+" priima SKAIČIUS- argumentai klasėje SKAIČIUS, TIKRAS- klasėje TIKRAS Ir SVEIKI SKAIČIUS- klasėje SVEIKI SKAIČIUS. Lygiagrečiai telefono paslaugų hierarchijos procedūrai pradėti klasėje PHONE_SERVICE gali prireikti argumento ADRESAS, nurodantis abonento adresą (atsiskaitymui), o klasėje ta pati procedūra CORPORATE_SERVICE reikės tokio argumento CORPORATE_ADDRESS.

Ryžiai. 17.6. Ryšio paslaugos

O kaip su prieštaringu sprendimu? Slidininkų pavyzdyje tai reikštų, kad jei pereinate į klasę RANKED_GIRL, rezultato tipas sugyventinis iš naujo apibrėžtas kaip RANKED_GIRL, tada dėl prieštaringumo argumento tipas Dalintis gali būti nepaisoma rašant MERGAITĖ arba SLIDININKAS. Vienintelis tipas, kuris neleidžiamas priešingame sprendime yra RANKED_GIRL! Pakanka, kad mergaičių tėvams sukeltų didžiausius įtarimus.

Lygiagrečios hierarchijos

Kad neliktų nė vieno akmens, panagrinėkime pavyzdžio variantą SLIDININKAS su dviem lygiagrečiomis hierarchijomis. Tai leis mums imituoti situaciją, su kuria jau susidūrėme praktiškai: TWO_WAY_LIST > LINKED_LIST Ir BI_LINKABLE > LINKABLE; arba hierarchija su telefono paslauga PHONE_SERVICE.

Tegul yra hierarchija su klase KAMBARYS, kurio palikuonis yra GIRL_ROOM(Klasė BERNIUKAS praleista):

Ryžiai. 17.7. Slidininkai ir kambariai

Vietoj to, mūsų slidininkų klasės šioje lygiagrečioje hierarchijoje sugyventinis Ir Dalintis turės panašius komponentus apgyvendinimas (apgyvendinimas) Ir apgyvendinti (paštu):


aprašymas: "Nauja parinktis su lygiagrečiomis hierarchijomis"
apgyvendinti (r: ROOM) yra ... reikalauti ... daryti

Kovariantų perrašymų reikia ir čia: klasėje MERGAITĖ1 Kaip apgyvendinimas, ir paprogramės argumentas apgyvendinti turi būti pakeistas tipu GIRL_ROOM, klasėje BERNIUKAS1- tipas BOY_ROOM ir tt (Atminkite, kad vis dar dirbame be tipo inkaravimo.) Kaip ir ankstesniame pavyzdyje, prieštaringumas čia nėra naudingas.

Polimorfizmo klastingumas

Ar nėra pakankamai pavyzdžių, patvirtinančių kovariacijos praktiškumą? Kodėl kas nors svarsto prieštaringumą, kuris prieštarauja tam, ko reikia praktikoje (išskyrus kai kurių jaunų žmonių elgesį)? Norėdami tai suprasti, apsvarstykite problemas, kylančias derinant polimorfizmo ir kovariacijos strategijas. Sugalvoti sabotažo schemą nėra sunku, o jūs galbūt jau esate ją sukūrę patys:


sukurti b; sukurti g;-- Sukurti BOY ir GIRL objektus.

Paskutinio iššūkio rezultatas, nors ir labai tikėtina, buvo malonus berniukams, yra būtent tai, ko mes stengėmės užkirsti kelią pasirinkdami tipą. Skambinti Dalintis veda prie to, kad objektas BERNIUKAS, žinomas kaip b ir polimorfizmo dėka gavo pseudonimą s tipo SLIDININKAS, tampa objekto kaimynu MERGAITĖ, žinomas kaip g. Tačiau iššūkis, nors ir prieštarauja nakvynės namų taisyklėms, programos tekste gana teisingas, nes Dalintis-eksportuotas komponentas kompozicijoje SLIDININKAS, A MERGAITĖ, argumento tipas g, suderinama su SLIDININKAS, formalaus parametro tipas Dalintis.

Lygiagrečios hierarchijos schema yra tokia pat paprasta: pakeiskite SLIDININKASįjungta SLIDININKAS1, iššūkis Dalintis- pagal iškvietimą s.commodate (gr), Kur gr- subjekto tipas GIRL_ROOM. Rezultatas toks pat.

Naudojant priešingą sprendimą, šios problemos nekiltų: skambučio tikslinės specializacija (mūsų pavyzdyje s) reikėtų apibendrinti argumentą. Kontravariacija lemia paprastesnį matematinį mechanizmo modelį: paveldėjimas – nepaisymas – polimorfizmas. Šis faktas aprašytas daugelyje teorinių straipsnių, kuriuose siūloma ši strategija. Argumentas nėra labai įtikinamas, nes, kaip rodo mūsų pavyzdžiai ir kitos publikacijos, prieštaringumas neturi praktinis naudojimas.

Todėl nesistengiant ant kovarianto kūno apsivilkti priešingų drabužių, reikėtų priimti kovariantinę realybę ir ieškoti būdų, kaip pašalinti nepageidaujamas poveikis.

Vaiko slėpimas

Prieš ieškodami kovariacijos problemos sprendimo, panagrinėkime kitą mechanizmą, kuris polimorfizmo sąlygomis gali sukelti tipo pažeidimus. Palikuonių slėpimas yra klasės gebėjimas neeksportuoti komponento, gauto iš savo tėvų.

Ryžiai. 17.8. Vaiko slėpimas

Tipiškas pavyzdys yra komponentas add_vertex(pridėti viršūnę) eksportuojama pagal klasę POLIGONAS, bet paslėptas jo palikuonių STAČIAKAMPIS(dėl galimo invarianto pažeidimo – klasė nori likti stačiakampiu):


Ne programuotojo pavyzdys: klasė "Strutis" slepia metodą "Musyti", gautą iš motinos "Paukštis".

Paimkime šią schemą tokią, kokia ji yra, ir paklauskime, ar paveldėjimo ir slėpimo derinys yra teisėtas. Modeliuojantis slėpimosi vaidmuo, kaip ir kovariacija, pažeidžiamas dėl polimorfizmo galimų gudrybių. Ir čia nesunku sukurti kenkėjišką pavyzdį, leidžiantį, nepaisant komponento slėpimo, jį iškviesti ir pridėti viršūnę prie stačiakampio:


sukurti r; -- Sukurkite objektą RECTANGLE.
p:= r; -- Polimorfinė užduotis.

Kadangi objektas r slepiasi po esme p klasė POLIGONAS, A add_vertex eksportuojamas komponentas POLIGONAS, tada jo iššūkis iš esmės p teisinga. Dėl vykdymo stačiakampyje atsiras kita viršūnė, o tai reiškia, kad bus sukurtas netinkamas objektas.

Sistemų ir klasių teisingumas

Norint aptarti kovariacijos ir vaikų slėpimo klausimus, reikia naujų terminų. Mes paskambinsime klasė galioja sistema, atitinkanti tris tipo aprašymo taisykles, pateiktas paskaitos pradžioje. Priminsime jiems: kiekviena esybė turi savo tipą; tikrojo argumento tipas turi būti suderinamas su formalaus argumento tipu, panaši situacija su priskyrimu; iškviečiamas pupelis turi būti deklaruotas savo klasėje ir eksportuotas į klasę, kurioje yra iškvietimas.

Sistema vadinama sistema galioja, jei jo vykdymo metu neįvyksta tipo pažeidimas.

Idealiu atveju abi sąvokos turėtų sutapti. Tačiau mes jau matėme, kad pagal klasę teisinga sistema paveldėjimo, kovariacijos ir palikuonių slėpimo sąlygomis gali būti neteisinga sistemai. Pavadinkime šią klaidą sistemos galiojimo klaidos pažeidimas.

Praktinis aspektas

Problemos paprastumas sukuria savotišką paradoksą: smalsus pradedantysis per kelias minutes sukurs priešingą pavyzdį; realiai praktikoje klaidų sistemų klasių teisingumui pasitaiko kasdien, tačiau sistemos teisingumo pažeidimai net didelėse -metų projektų, pasitaiko itin retai.

Tačiau tai neleidžia mums jų ignoruoti, todėl pradedame tyrinėti tris galimus šios problemos sprendimo būdus.

Toliau paliesime labai subtilius ir ne taip dažnai akivaizdžius objekto požiūrio aspektus. Jei skaitote knygą pirmą kartą, galite praleisti likusias šios paskaitos dalis. Jei tik neseniai ėmėtės OO technologijos, šią medžiagą geriau suprasite išstudijavę „Objektinio projektavimo pagrindai“ kurso 1-11 paskaitas, skirtas paveldėjimo metodikai, ir ypač 6 paskaitą „Objektinio projektavimo pagrindai“. Orientuotas dizainas“ kursas, skirtas metodikos paveldėjimui.

Sistemų teisingumas: pirmasis aproksimavimas

Pirmiausia sutelkime dėmesį į kovariacijos problemą, kuri yra svarbesnė iš šių dviejų. Šiai temai skirta didelė literatūra, siūlanti daugybę skirtingų sprendimų.

Kontravariacija ir nedispersija

Kontravariacija pašalina teorines problemas, susijusias su sistemos teisingumo pažeidimu. Tačiau tai praranda tipo sistemos tikroviškumą, todėl šio požiūrio toliau svarstyti nereikia.

C++ kalbos originalumas yra tas, kad ji naudoja strategiją novariacija, neleisdamas pakeisti argumentų tipo nepaisomose rutinose! Jei C++ būtų stipriai spausdinama kalba, taip būtų sistemos tipai būtų sunku naudotis. Paprasčiausias problemos sprendimas šia kalba, taip pat apeinant kitus C++ apribojimus (pavyzdžiui, riboto universalumo nebuvimą), yra naudoti liejimo – tipo liejimą, kuris leidžia visiškai nepaisyti esamo spausdinimo mechanizmo. Šis sprendimas neatrodo patrauklus. Tačiau atkreipkite dėmesį, kad daugelis toliau aptartų pasiūlymų bus grindžiami nevariacija, kurios prasmė bus suteikta įdiegus naujus mechanizmus, skirtus dirbti su tipais, o ne kovariantinį apibrėžimą.

Bendrųjų parametrų naudojimas

Universalumas yra įdomios idėjos, kurią pirmą kartą išreiškė Franz Weber, esmė. Paskelbkime klasę SLIDININKAS1, apribojant bendrojo parametro universalizavimą iki klasės KAMBARYS:


klasės SKIER1 funkcija
apgyvendinti (r: G) yra ... reikalauti ... daryti apgyvendinimą:= r galas

Tada klasė MERGAITĖ1 bus įpėdinis SLIDININKAS1 Tą pačią techniką, kad ir kaip keistai ji atrodytų iš pirmo žvilgsnio, galima naudoti ir nesant lygiagrečios hierarchijos: klasės slidininkas.

Šis metodas išsprendžia kovariacijos problemą. Bet koks klasės naudojimas turi nurodyti tikrąjį bendrąjį parametrą KAMBARYS arba GIRL_ROOM, todėl neteisingas derinys tampa tiesiog neįmanomas. Kalba tampa be variantų, o sistema visiškai atitinka kovariacijos poreikius dėl bendrųjų parametrų.

Deja, šis metodas nėra tinkamas kaip bendras sprendimas, nes dėl jo didėja bendrųjų parametrų sąrašas, po vieną kiekvienam galimo kovarianto argumento tipui. Dar blogiau, pridedant kovariantinę paprogramę su argumentu, kurio tipo sąraše nėra, reikės pridėti bendrąjį klasės parametrą, todėl bus pakeista klasės sąsaja, sukeldama pakeitimus visiems klasės klientams, o tai yra nepriimtina.

Tipiniai kintamieji

Nemažai autorių, įskaitant Kimą Bruce'ą, Davidą Shangą ir Tony Simonsą, pasiūlė sprendimą, pagrįstą tipo kintamaisiais, kurių reikšmės yra tipai. Jų idėja paprasta:

[x]. vietoj kovarianto nepaisymo leisti tipo deklaracijas, kuriose naudojami tipo kintamieji;

[x]. išplėsti tipų suderinamumo taisykles, kad būtų galima valdyti tokius kintamuosius;

[x]. suteikti galimybę priskirti kalbos tipus kaip reikšmes tipo kintamiesiems.

Išsamų šių idėjų pristatymą skaitytojai gali rasti daugelyje straipsnių šia tema, taip pat Cardelli, Castagna, Weber ir kt. leidiniuose. Galite pradėti studijuoti šią problemą iš šaltinių, nurodytų šios paskaitos bibliografinėse pastabose. . Mes nespręsime šios problemos ir štai kodėl.

[x]. Tinkamai įgyvendintas tipo kintamojo mechanizmas patenka į kategoriją, kuri leidžia naudoti tipą jo visiškai nenurodant. Ta pati kategorija apima universalumą ir skelbimų prisegimą. Šis mechanizmas galėtų pakeisti kitus šios kategorijos mechanizmus. Iš pradžių tai gali būti aiškinama tipo kintamųjų naudai, tačiau rezultatas gali būti pražūtingas, nes neaišku, ar šis visa apimantis mechanizmas gali atlikti visas užduotis taip lengvai ir paprastai, kaip būdinga universalumui ir tipo tvirtinimui.

[x]. Tarkime, kad yra sukurtas tipo kintamojo mechanizmas, galintis įveikti kovariacijos ir polimorfizmo derinimo problemas (vis tiek ignoruojant vaiko slėpimo problemą). Tada reikės klasės kūrėjo nepaprasta intuicija siekiant iš anksto nuspręsti, kurie komponentai bus prieinami sugeneruotų klasių nepakeičiamiems tipams, o kurie ne. Žemiau aptarsime šią problemą, kuri iškyla programų kūrimo praktikoje ir, deja, kelia abejonių dėl daugelio teorinių schemų pritaikomumo.

Tai verčia grįžti prie jau aptartų mechanizmų: riboto ir neriboto universalumo, tipo inkaravimo ir, žinoma, paveldėjimo.

Remdamiesi tipo tvirtinimu

Mes rasime beveik paruoštą kovariacijos problemos sprendimą, atidžiau pažvelgę ​​į mums žinomą lipnių skelbimų mechanizmą.

Apibūdinant klases SLIDININKAS Ir SLIDININKAS1 Negalėjote atsisakyti pagundos naudoti prisegtus skelbimus, kad atsikratytumėte daugybės nepaisymų. Inkaravimas yra tipiškas kovariantinis mechanizmas. Štai kaip atrodytų mūsų pavyzdys (visi pakeitimai pabraukti):


dalintis (kita: kaip Dabartinis) yra ... reikalauti ... daryti
apgyvendinti (r: kaip apgyvendinimas) yra ... reikalauti ... daryti

Dabar palikuonys gali palikti klasę SLIDININKAS jokių pokyčių, bet SLIDININKAS1 jiems reikės tik nepaisyti atributo apgyvendinimas. Prisegti objektai: atributas sugyventinis ir paprogramės argumentai Dalintis Ir apgyvendinti- pasikeis automatiškai. Tai labai supaprastina darbą ir sustiprina faktą, kad nesant prisegimo (ar kito panašaus mechanizmo, pavyzdžiui, tipo kintamųjų), neįmanoma parašyti objektinio programinės įrangos produkto su tikrovišku spausdinimu.

Bet ar buvo įmanoma pašalinti sistemos teisingumo pažeidimus? Ne! Vis tiek galime pergudrauti tipo tikrinimą, atlikdami polimorfines priskyrimus, dėl kurių pažeidžiami sistemos teisingumo pažeidimai.

Tačiau originalios pavyzdžių versijos bus atmestos. Leisti būti:


sukurti b;kurti g;-- Sukurti BOY ir GIRL objektus.
s:= b; -- Polimorfinė užduotis.

Argumentas g, perduota Dalintis, dabar yra neteisingas, nes jam reikalingas tipo objektas kaip s, ir klasė MERGAITĖ nesuderinamas su šiuo tipu, nes pagal įtvirtinto tipo taisyklę joks tipas nesuderinamas kaip s išskyrus jį patį.

Tačiau džiaugsimės neilgai. Kita vertus, ši taisyklė tai sako kaip s suderinamas su tipu s. Tai reiškia, kad reikia naudoti ne tik objekto polimorfizmą s, bet ir parametras g, galime dar kartą apeiti tipo tikrinimo sistemą:


s: slidininkas; b: BERNIUKAS; g: patinka s; faktinis_g: MERGAINA;
sukurti b; sukurti aktualų_g – sukurti objektus BOY ir GIRL.
s:= faktinis_g; g:= s – naudokite s, kad pridėtumėte g prie GIRL.
s:= b -- Polimorfinis priskyrimas.

Dėl to neteisėtas skambutis praeina.

Yra išeitis. Jei rimtai norime naudoti deklaracijų prisegimą kaip vienintelį kovariacijos mechanizmą, tai galime atsikratyti sistemos teisingumo pažeidimų visiškai uždraudę prisegtų objektų polimorfizmą. Tam reikės pakeisti kalbą: įveskime naują raktinį žodį inkaras(šios hipotetinės konstrukcijos mums reikia tik šioje diskusijoje):


Leiskime pateikti formos deklaracijas kaip s tik tada, kai s aprašyta kaip inkaras. Pakeisime suderinamumo taisykles, siekdami užtikrinti: s ir tokie elementai kaip kaip s gali būti pririšti (užduotyse ar argumentų perdavimo metu) tik vienas prie kito.

Taikydami šį metodą iš kalbos pašaliname galimybę nepaisyti bet kokių paprogramių argumentų tipo. Be to, galime neleisti nepaisyti rezultato tipo, bet tai nėra būtina. Žinoma, galimybė iš naujo apibrėžti atributo tipą išlieka. Visi Argumento tipo nepaisymas dabar bus daromas netiesiogiai naudojant lipnų mechanizmą, kurį suaktyvina kovariacija. Kur, naudojant ankstesnį požiūrį, klasė D iš naujo apibrėžė paveldėtą komponentą kaip:


kadangi klasė C- tėvas D atrodė


Kur Y susirašinėjo X, tada dabar komponento nepaisymas r atrodys taip:


Lieka tik klasėje D nepaisymo tipas jūsų_inkaras.

Šį kovariacijos – polimorfizmo problemos sprendimą vadinsime požiūriu Inkaravimas. Tiksliau būtų sakyti: „Kovariacija tik per konsolidavimą“. Požiūrio savybės yra patrauklios:

[x]. Sustiprinimas grindžiamas griežto atskyrimo idėja kovariantas ir potencialiai polimorfiniai (arba trumpiau – polimorfiniai) elementai. Visi subjektai, deklaruoti kaip inkaras arba kaip koks_inkaras kovariantas; kiti yra polimorfiniai. Kiekvienoje iš dviejų kategorijų leidžiama naudoti bet kokią priedą, tačiau nėra objekto ar posakio, kuris pažeidžia ribą. Pavyzdžiui, negalite priskirti polimorfinio šaltinio kovariantiniam tikslui.

[x].Šį paprastą ir elegantišką sprendimą lengva paaiškinti net pradedantiesiems.

[x]. Tai visiškai pašalina galimybę pažeisti sistemos teisingumą kovariantiškai sukonstruotose sistemose.

[x]. Ji išlaiko aukščiau išdėstytą konceptualų pagrindą, įskaitant riboto ir neriboto universalumo sąvokas. (Dėl to šis sprendimas, mano nuomone, yra geresnis už standartinius kintamuosius, pakeičiančius kovariacijos ir universalumo mechanizmus, skirtus įvairioms praktinėms problemoms spręsti.)

[x]. Tam reikia šiek tiek pakeisti kalbą – pridėti vieną raktinį žodį, atsispindintį atitikties taisyklėje – ir nesukelia jokių didelių įgyvendinimo sunkumų.

[x]. Tai realu (bent jau teoriškai): bet kurią anksčiau galimą sistemą galima perrašyti pakeičiant kovariantus nepaisymus lipniomis pakartotinėmis deklaracijomis. Tiesa, dėl to kai kurie sujungimai bus negaliojantys, tačiau jie atitinka atvejus, dėl kurių gali būti pažeisti tipai, todėl juos reikėtų pakeisti priskyrimo bandymais ir tvarkyti vykdymo metu.

Atrodytų, čia diskusija gali baigtis. Taigi kodėl nesame visiškai patenkinti sustiprinimo metodu? Visų pirma, mes dar nepalietėme vaiko slėpimo klausimo. Be to, pagrindinė priežastis tęsti diskusiją yra problema, jau išsakyta trumpai paminėjus tipo kintamuosius. Įtakos sferų padalijimas į polimorfines ir kovariantines dalis yra šiek tiek panašus į Jaltos konferencijos rezultatą. Daroma prielaida, kad klasės kūrėjas turi nepaprastą intuiciją, kurią jis gali kiekvienam įvestam objektui, ypač kiekvienam argumentui, kartą ir visiems laikams pasirinkti vieną iš dviejų galimybių:

[x]. Esybė yra potencialiai polimorfinė: dabar arba vėliau (perduodant parametrus arba priskirdama) gali būti prijungta prie objekto, kurio tipas skiriasi nuo deklaruojamo. Pradinio objekto tipo negali pakeisti joks klasės palikuonis.

[x]. Esybė yra tipo naujo apibrėžimo objektas, tai yra, jis yra fiksuotas arba pats yra pagalbinis elementas.

Bet kaip kūrėjas gali visa tai numatyti? Visas OO metodo patrauklumas, didžiąja dalimi išreikštas principu Atviras-Uždarytas, yra būtent susijęs su galimybe keisti anksčiau atliktus darbus, taip pat su tuo, kad universalių sprendimų kūrėjas Ne turi turėti begalinę išmintį, suprasti, kaip jo produktas gali būti pritaikytas jo palikuonių poreikiams.

Taikant šį metodą, tipo nepaisymas ir vaiko slėpimas yra tam tikras „apsauginis vožtuvas“, leidžiantis pakartotinai panaudoti esamą klasę, kuri beveik tinka mūsų tikslams pasiekti:

[x]. Naudodami tipo nepaisymą, galime pakeisti išvestinės klasės deklaracijas nepaveikdami originalo. Tokiu atveju grynai kovariantiniam sprendimui reikės redaguoti originalą naudojant aprašytas transformacijas.

[x]. Vaiko pasislėpimas apsaugo nuo daugelio nesėkmių kuriant klasę. Galima kritikuoti projektą, kuriame STAČIAKAMPIS, pasinaudodamas tuo, kad jis yra palikuonis POLIGONAS, bando pridėti viršūnę. Vietoj to būtų galima pasiūlyti paveldėjimo struktūrą, kurioje figūros su fiksuotu viršūnių skaičiumi būtų atskirtos nuo visų kitų, ir problemos nekiltų. Tačiau kuriant paveldėjimo struktūras, tos, kuriose nėra taksonominės išimtys. Bet ar galima juos visiškai pašalinti? Kai vėlesnėje paskaitoje aptarsime eksporto apribojimus, pamatysime, kad tai neįmanoma dėl dviejų priežasčių. Pirmasis yra konkuruojančių klasifikavimo kriterijų buvimas. Antra, yra galimybė, kad kūrėjas neras tobulo sprendimo, net jei jis egzistuoja.

Globali analizė

Šis skyrius skirtas tarpiniam požiūriui apibūdinti. Pagrindiniai praktiniai sprendimai pateikiami 17 paskaitoje.

Studijuodami prisegimo variantą pastebėjome, kad pagrindinė jo idėja buvo kovariantinių ir polimorfinių objektų rinkinių atskyrimas. Taigi, jei paimtume du formos nurodymus


kiekvienas iš jų yra svarbių OO mechanizmų teisingo taikymo pavyzdys: pirmasis yra polimorfizmas, antrasis – tipo pakartotinis apibrėžimas. Problemos prasideda derinant jas tam pačiam subjektui s. Taip pat:


problemos prasideda nuo dviejų nepriklausomų ir visiškai nekaltų operatorių derinio.

Netinkami skambučiai sukelia tipo pažeidimus. Pirmajame pavyzdyje polimorfinis priskyrimas prideda objektą BERNIUKAS iki taško s, ką jis daro g neteisingas argumentas Dalintis, nes jis yra susietas su objektu MERGAITĖ. Antrame pavyzdyje subjektui r objektas prisijungia STAČIAKAMPIS, kuri neįtraukia add_vertex iš eksportuojamų komponentų.

Čia yra naujo sprendimo idėja: iš anksto – statiškai, tikrindami tipus kompiliatoriumi ar kitais įrankiais – apibrėžiame rinkinys kiekvieną objektą, įskaitant objektų tipus, su kuriais objektą galima susieti vykdymo metu. Tada vėl statiškai įsitikiname, kad kiekvienas iškvietimas yra teisingas kiekvienam tikslinio tipo elementui ir argumentų rinkiniams.

Mūsų pavyzdžiuose operatorius s:=b rodo, kad klasė BERNIUKAS priklauso tipų rinkiniui s(nes dėl kūrimo instrukcijos vykdymo sukurti b jis priklauso tipų rinkiniui b). MERGAITĖ, dėl instrukcijų buvimo sukurti g, priklauso tipų rinkiniui g. Bet tada iššūkis Dalintis bus nepriimtina šiam tikslui s tipo BERNIUKAS ir argumentas g tipo MERGAITĖ. taip pat STAČIAKAMPIS yra nustatyto tipo p, kuris yra dėl polimorfinio priskyrimo, tačiau skambutis add_vertex Dėl p tipo STAČIAKAMPIS pasirodys nepriimtina.

Šie pastebėjimai verčia susimąstyti apie kūrimą globalus metodas, pagrįstas nauja spausdinimo taisykle:

Sistemos teisingumo taisyklė

Skambinti x.f(arg) yra sistemai tinkama tada ir tik tada, kai ji yra tinkama klasei x, Ir arg, turintys bet kokius tipus iš atitinkamų tipų rinkinių.

Šiame apibrėžime skambutis laikomas klase teisingu, jei jis nepažeidžia komponentų iškvietimo taisyklės, kuri teigia: jei C yra pagrindinė tipo klasė x, komponentas f turi būti eksportuojama C, ir įveskite arg turi būti suderinamas su formalaus parametro tipu f. (Atminkite: dėl paprastumo darome prielaidą, kad kiekviena rutina turi tik vieną parametrą, tačiau nėra sunku išplėsti taisyklę iki savavališko skaičiaus argumentų.)

Sisteminis skambučio teisingumas sumažinamas iki klasės teisingumo, išskyrus tai, kad tikrinama ne atskiri elementai, o bet kokios poros iš aibių rinkinių. Toliau pateikiamos pagrindinės kiekvieno objekto tipų rinkinio kūrimo taisyklės:

1 Kiekvieno objekto pradinis tipų rinkinys yra tuščias.

2 Susidūręs su kitu formos nurodymu sukurti (SOME_TYPE)a, papildyti SOME_TYPEį tipų rinkinį a. (Paprastumo dėlei manysime, kad bet kuri instrukcija sukurti bus pakeistas instrukcijomis sukurti(ATIPAS)a, Kur ATIPAS- subjekto tipas a.)

3 Susidūręs su kita formos užduotimi a:= b, pridėkite prie tipų rinkinio, skirto a b.

4 Jeigu a yra formalus paprogramės parametras, tada kitas iškvietimas susiduria su tikruoju parametru b, pridėkite prie tipų rinkinio, skirto a visi nustatyto tipo elementai b.

5 Kartosime (3) ir (4) veiksmus, kol tipų rinkiniai nustos keistis.

Šioje formuluotėje neatsižvelgiama į universalumo mechanizmą, tačiau prireikus taisyklę be problemų galima išplėsti. 5 veiksmas yra būtinas dėl galimybės priskirti ir perduoti grandines (nuo bĮ a, nuo cĮ b ir tt). Nesunku suprasti, kad po riboto skaičiaus žingsnių šis procesas sustos.

Kaip tikriausiai pastebėjote, taisyklė neatsižvelgia į instrukcijų seką. Kada


sukurti(TYPE1)t; s:=t; sukurti (TIPAS2) t

į tipų rinkinį s ateis kaip 1 TIPAS, taip TIPAS2, Nors s, atsižvelgiant į instrukcijų seką, gali priimti tik pirmojo tipo reikšmes. Atsižvelgiant į instrukcijų vietą, kompiliatorius turės nuodugniai išanalizuoti komandų srautą, o tai pernelyg padidins algoritmo sudėtingumą. Vietoj to galioja pesimistiškesnės taisyklės: operacijų seka yra tokia:


bus paskelbti sistemos neteisingais, nepaisant to, kad jų vykdymo seka nesukelia tipo pažeidimo.

Visuotinė sistemos analizė buvo pateikta (išsamiau) monografijos 22 skyriuje. Kartu buvo išspręsta ir kovariacijos, ir eksporto apribojimų paveldėjimo metu problema. Tačiau šis metodas turi erzinantį praktinį trūkumą, būtent: jis reikalauja tikrinimo sistema kaip visuma, o ne kiekviena klasė atskirai. Žudikas pasirodo esanti taisyklė (4), kuri, iškvietusi bibliotekos paprogramę, atsižvelgs į visus galimus jos skambučius kitose klasėse.

Nors tada buvo pasiūlyti darbo su atskiromis klasėmis algoritmai, jų praktinės vertės nustatyti nepavyko. Tai reiškė, kad programavimo aplinkoje, kurioje palaikomas laipsniškas kompiliavimas, reikės išbandyti visą sistemą. Patartina čekį įvesti kaip (greito) vietinio vartotojo atliktų kai kurių klasių pakeitimų apdorojimo elementą. Nors žinomi globalaus požiūrio panaudojimo pavyzdžiai, pavyzdžiui, C programuotojai naudoja įrankį pūkelių rasti sistemos neatitikimų, kurių neaptinka kompiliatorius - visa tai neatrodo labai patrauklu.

Dėl to sistemos teisingumo patikrinimas, kiek žinau, liko niekieno neįgyvendintas. (Kita tokio rezultato priežastis galėjo būti pačių patikrinimo taisyklių sudėtingumas.)

Klasės teisingumas apima tikrinimą, apribotą klase, todėl tai įmanoma naudojant laipsnišką kompiliavimą. Sistemos teisingumas suponuoja visuotinį visos sistemos patikrinimą, o tai prieštarauja laipsniškam kompiliavimui.

Tačiau, nepaisant pavadinimo, iš tikrųjų galima patikrinti sistemos teisingumą naudojant tik laipsnišką klasės tikrinimą (paleidžiant įprastą kompiliatorių). Tai bus galutinis indėlis į problemos sprendimą.

Saugokitės polimorfinių šauksmų!

Sistemos teisingumo taisyklė yra pesimistinė: dėl paprastumo ji atmeta visiškai saugius instrukcijų derinius. Paradoksalu, bet remdamiesi pastatysime paskutinį sprendimą dar pesimistiškesnė taisyklė. Natūralu, kad tai iškels klausimą, kiek realus bus mūsų rezultatas.

Atgal į Jaltą

Sprendimo esmė Catcall, - šios sąvokos prasmę paaiškinsime vėliau, - grįžtant prie Jaltos susitarimų dvasios, skirstant pasaulį į polimorfinį ir kovariantinį (o kovariacijos palydovas yra palikuonių slėpimas), bet be būtinybės turėti begalinė išmintis.

Kaip ir anksčiau, kovariacijos klausimą susiaurinkime iki dviejų operacijų. Pagrindiniame mūsų pavyzdyje tai yra polimorfinė užduotis: s:=b, ir iškvieskite kovarianto paprogramę: s.share (g). Analizuodami, kas yra tikrasis pažeidimų kaltininkas, argumentą atmesime g iš įtariamųjų. Bet koks argumentas SLIDININKAS arba iš jo kilęs, mums netinka dėl polimorfizmo s ir kovariacija Dalintis. Todėl jei statiškai aprašome esybę kitas Kaip SLIDININKAS ir dinamiškai pritvirtinti prie objekto SLIDININKAS, tada skambutis s.share (kita) statiškai sudarys idealaus varianto įspūdį, tačiau priskiriant polimorfiškai sukels tipo pažeidimus s prasmė b.

Pagrindinė problema yra ta, kad mes stengiamės naudoti s dviem nesuderinamais būdais: kaip polimorfinis subjektas ir kaip kovariacinės paprogramės iškvietimo taikinys. (Kitame mūsų pavyzdyje problema yra naudojant p kaip polimorfinis subjektas ir kaip antrinės paprogramės iškvietimo, slepiančio komponentą, tikslas add_vertex.)

Catcall sprendimas, kaip ir konsolidavimas, yra radikalus: jis draudžia naudoti objektą ir kaip polimorfinį, ir kaip kovariantinį. Kaip ir visuotinis analizavimas, jis statiškai nustato, kurie objektai gali būti polimorfiniai, bet nesistengia būti pernelyg gudrūs, ieškodami galimų objektų tipų rinkinių. Vietoj to, bet koks polimorfinis subjektas yra suvokiamas kaip pakankamai įtartinas ir jam draudžiama sudaryti sąjungą su garbingų asmenų ratu, įskaitant kovariaciją ir palikuonių slėpimąsi.

Viena taisyklė ir keli apibrėžimai

„Catcall“ sprendimo tipo taisyklė turi paprastą formulę:

„Catcall“ tipo taisyklė

Polimorfiniai skambučiai yra neteisingi.

Jis pagrįstas vienodai paprastais apibrėžimais. Visų pirma, polimorfinis subjektas:

Apibrėžimas: polimorfinis subjektas

Esmė x Referencinis (neišplėstas) tipas yra polimorfinis, jei jis turi vieną iš šių savybių:

1 Atsiranda užduotyje x:=y, kur yra subjektas y turi skirtingą tipą arba yra polimorfinis pagal rekursiją.

2 Rasti kūrimo instrukcijose sukurti (OTHER_TYPE)x, Kur OTHER_TYPE nėra deklaracijoje nurodyto tipo x.

3 Ar formalus paprogramės argumentas.

4 Yra išorinė funkcija.

Šio apibrėžimo tikslas – suteikti polimorfiškumo ("potencialiai polimorfinio") statusą bet kokiai esybei, kuri programos vykdymo metu gali būti prijungta prie skirtingų tipų objektų. Šis apibrėžimas taikomas tik nuorodų tipams, nes išplėsti objektai iš prigimties negali būti polimorfiniai.

Mūsų pavyzdžiuose slidininkas s ir daugiakampis p- polimorfinis pagal (1) taisyklę. Pirmajam iš jų priskiriamas objektas BERNIUKAS g, antrasis – objektas STAČIAKAMPIS.

Jei esate susipažinę su tipų rinkinio sąvokos formulavimu, pastebėsite, kiek pesimistiškiau atrodo polimorfinio subjekto apibrėžimas ir kiek lengviau jį patikrinti. Nebandydami rasti visų įmanomų dinaminių esybės tipų, pasitenkiname bendru klausimu: ar tam tikra esybė gali būti polimorfinė, ar ne? Labiausiai stebina taisyklė (3), pagal kurią polimorfinis skaičiuoja kiekvienas formalus parametras(nebent jo tipas yra išplėstas, kaip tai daroma su sveikaisiais skaičiais ir pan.). Net nesivarginame analizuoti iššūkių. Jei paprogramė turi argumentą, tada jis yra visiškai kliento žinioje, o tai reiškia, kad deklaracijoje nurodytu tipu negalima pasikliauti. Ši taisyklė yra glaudžiai susijusi su pakartotinai naudoti- objektų technologijos paskirtis, - kur bet kuri klasė gali būti įtraukta į biblioteką ir bus pakartotinai iškviesta skirtingų klientų.

Būdingas šios taisyklės bruožas yra tai, kad jai nereikia jokių visuotinių patikrinimų. Esybės polimorfizmui nustatyti pakanka pažvelgti į pačios klasės tekstą. Jei visoms užklausoms (atributams ar funkcijoms) saugoma informacija apie jų polimorfinę būseną, tada jums net nereikia studijuoti protėvių tekstų. Skirtingai nei ieškant tipų rinkinių, galite atrasti polimorfinius objektus, tikrindami klasę pagal klasę laipsniško kompiliavimo procese.

Skambučiai, kaip ir subjektai, gali būti polimorfiniai:

Apibrėžimas: polimorfinis skambutis

Skambutis yra polimorfinis, jei jo taikinys yra polimorfinis.

Abu mūsų pavyzdžiuose esantys skambučiai yra polimorfiniai: s.share (g) dėl polimorfizmo s, p.add_vertex(...) dėl polimorfizmo p. Pagal apibrėžimą tik kvalifikuoti skambučiai gali būti polimorfiniai. (Nekvalifikuotas skambutis f (...) savotiškas kvalifikuotas Current.f (...), mes nekeičiame reikalo esmės, nes Dabartinė, kuriai nieko negalima priskirti, nėra polimorfinis objektas.)

Toliau mums reikia Catcall koncepcijos, kuri yra pagrįsta CAT koncepcija. (CAT yra prieinamumo arba tipo keitimo akronimas). Paprogramė yra CAT paprogramė, jei ją iš naujo apibrėžia palikuonis, dėl kurio atsiranda vienas iš dviejų pakeitimų, kurie, kaip matėme, gali būti pavojingi: pakeičiamas argumento tipas (kovariantiškai) arba slepiamas anksčiau eksportuotas komponentas.

Apibrėžimas: CAT rutina

Įprasta tvarka vadinama CAT rutina, jei jos nepaisymas pakeičia eksporto būseną arba kurio nors iš jos argumentų tipą.

Šiai ypatybei vėl taikomas laipsniškas testavimas: bet koks argumento tipo ar eksporto būsenos nepaisymas paverčia procedūrą arba funkciją CAT paprograme. Tai veda prie Catcall koncepcijos: skambutis į CAT rutiną, kuri gali nepavykti.

Apibrėžimas: Catcall

Iškvietimas vadinamas Catcall, jei dėl paprogramės nepaisymo jis nepavyktų dėl eksporto būsenos arba argumento tipo pasikeitimo.

Mūsų sukurta klasifikacija leidžia identifikuoti specialias iškvietimų grupes: polimorfinius ir kačių skambučius. Polimorfiniai skambučiai suteikia išraiškingos galios objekto metodui, o raginimai leidžia nepaisyti tipų ir apriboti eksportą. Naudojant anksčiau šioje paskaitoje pristatytą terminiją, polimorfiniai skambučiai tęsiasi naudingumo, kačių skambučiai - tinkamumas naudoti.

Iššūkiai Dalintis Ir add_vertex, aptariami mūsų pavyzdžiuose, yra kačių skambučiai. Pirmasis atlieka kovariantinį savo argumento apibrėžimą. Antrąjį eksportuoja klasė STAČIAKAMPIS, bet klasė paslėpta POLIGONAS. Abu skambučiai taip pat yra polimorfiniai, todėl tarnauja puikus pavyzdys polimorfiniai šauksmai. Jie yra klaidingi pagal Catcall tipo taisyklę.

Įvertinimas

Prieš sudėliodami viską, ką sužinojome apie kovariaciją ir vaikų slėpimą, dar kartą prisiminkime, kad sistemų teisingumo pažeidimai išties reti. Paskaitos pradžioje buvo apibendrintos svarbiausios statinio objektinio tipavimo savybės. Šis įspūdingas tipų manipuliavimo mechanizmų rinkinys kartu su klasės teisingumo tikrinimu atveria kelią saugiam ir lanksčiam programinės įrangos kūrimo metodui.

Matėme tris kovariacijos problemos sprendimus, iš kurių du taip pat buvo susiję su eksporto apribojimais. Kuris yra teisingas?

Nėra galutinio atsakymo į šį klausimą. Klastingos OO tipavimo ir polimorfizmo sąveikos pasekmės nėra taip gerai suprantamos kaip ankstesnėse paskaitose aptartos problemos. Pastaraisiais metais šia tema pasirodė daug publikacijų, kurių nuorodos pateikiamos paskaitos pabaigoje esančioje bibliografijoje. Be to, tikiuosi, kad šioje paskaitoje man pavyko pristatyti galutinio sprendimo elementus ar bent jau prie jo priartėti.

Pasaulinė analizė atrodo nepraktiška dėl to pilnas patikrinimas visa sistema. Tačiau tai padėjo mums geriau suprasti problemą.

Pinning sprendimas yra itin patrauklus. Tai paprasta, intuityvi ir lengvai įgyvendinama. Tuo labiau turėtume apgailestauti, kad jame neįmanoma remti daugelio pagrindinių OO metodo reikalavimų, atsispindinčių atviro-uždarymo principu. Jei tikrai turėtume puikią intuiciją, tada prisegimas būtų puikus sprendimas, bet koks kūrėjas išdrįstų tai pasakyti, o juolab pripažinti, kad tokią intuiciją turėjo jo projekte paveldėtų bibliotekos užsiėmimų autoriai?

Jei esame priversti atsisakyti konsolidacijos, Catcall sprendimas atrodo tinkamiausias, gana lengvai paaiškinamas ir pritaikomas praktikoje. Jo pesimizmas neturėtų atmesti naudingų operatorių derinių. Tuo atveju, kai polimorfinį skambutį sugeneruoja „teisėtas“ operatorius, visada galima saugiai tai pripažinti įvedant priskyrimo bandymą. Taigi į programos vykdymo laiką galima perkelti daugybę patikrinimų. Tačiau tokių atvejų skaičius turėtų būti itin mažas.

Norėdami paaiškinti, turėčiau pažymėti, kad rašymo metu Catcall sprendimas nebuvo įgyvendintas. Kol kompiliatorius nėra pritaikytas patikrinti Catcall tipo taisyklę ir sėkmingai pritaikytas didelėms ir mažoms reprezentacinėms sistemoms, dar anksti teigti, kad paskutinis žodis buvo pasakytas dėl statinio tipavimo derinimo su polimorfizmu kartu su kovariacija ir vaiko slėpimu. .

Visiškas atitikimas

Baigiant mūsų diskusiją apie kovariaciją, naudinga suprasti, kaip bendras metodas gali būti taikomas gana bendrai problemai. Metodas atsirado kaip Catcall teorijos rezultatas, tačiau jį galima naudoti pagrindinėje kalbos versijoje neįvedant naujų taisyklių.

Tebūnie du suderinti sąrašai, kur pirmame nurodomi slidininkai, o antrame – sugyventinis slidininkui iš pirmojo sąrašo. Norime laikytis atitinkamos įdarbinimo procedūros Dalintis, tik jei tai leidžia tipo aprašymo taisyklės, leidžiančios merginoms atsiskaityti su merginomis, prizininkės – su prizinėmis merginomis ir pan. Tokio tipo problemos yra dažnos.

Gali būti paprastas sprendimas, pagrįstas ankstesne diskusija ir bandymu atlikti užduotį. Apsvarstykite universalią funkciją įrengtas(patvirtinti):


įrengtas (kitas: BENDRAS): kaip ir kitas yra
- Dabartinis objektas (dabartinis), jei jo tipas atitinka objekto tipą,
-- prijungtas prie kito, kitu atveju negaliojantis.
if other /= Void and then conforms_to (kita) tada

Funkcija įrengtas grąžina dabartinį objektą, bet žinomą kaip prie argumento pridėto tipo objektą. Jei dabartinio objekto tipas nesutampa su prie argumento pridėto objekto tipu, tada Tuštuma. Atkreipkite dėmesį į užduoties bandymo vaidmenį. Funkcija naudoja komponentą atitinka_ iš klasės BENDROJI, kuris nustato poros objektų tipo suderinamumą.

Pakeitimas atitinka_į kitą komponentą BENDROJI Su vardu tas pats_tipas suteikia mums funkciją tobulai pritaikytas (visiško atitikimo), kuris grįžta Tuštuma, jei abiejų objektų tipai nėra tapatūs.

Funkcija įrengtas- suteikia mums paprastą slidininkų atitikimo problemos sprendimą nepažeidžiant tipo aprašymo taisyklių. Taigi, klasės kode SLIDININKAS galime įvesti naują procedūrą ir vietoje jos naudoti Dalintis, (pastarąją galima atlikti kaip paslėptą procedūrą).


-- Jei įmanoma, pasirinkite kitą kaip kaimyną pagal numerį.
-- gender_scertained - priskirta lytis
gender_assertained_other: patinka Dabartinis
gender_ascertained_other:= kita .fitted (dabartinė)
if gender_assertained_other /= Tuščia tada
dalintis (lytis_patvirtinta_kita)
„Išvada: gyventi kartu su kitais negalima“

Dėl kitas savavališkas tipas SLIDININKAS(ne tik kaip Dabartinė) nustatykite versiją lytis_tvirtinta_kita, kuriam priskirtas tipas Dabartinė. Funkcija padės mums garantuoti tipų tapatumą tobulai pritaikytas.

Jei yra du lygiagrečiai suplanuotą būstą atstovaujančių slidininkų sąrašai:


occupant1, occupant2: LIST

Galite organizuoti kilpą skambindami kiekviename žingsnyje:


occupant1.item.safe_share (occupant2.item)

atitinkančius sąrašų elementus tada ir tik tada, kai jų tipai yra visiškai suderinami.

Pagrindinės sąvokos

[x]. Statinis spausdinimas yra raktas į patikimumą, skaitomumą ir efektyvumą.

[x]. Kad būtų realu, statiniam spausdinimui reikalingas mechanizmų derinys: tvirtinimai, daugkartinis paveldėjimas, bandymas priskirti, ribotas ir neribotas universalumas, įtvirtintos deklaracijos. Tipo sistema neturėtų leisti spąstų (tipo užmetimų).

[x]. Perdeklaravimo taisyklės turėtų leisti iš naujo apibrėžti kovariantą. Nepaisyti rezultatų ir argumentų tipai turi būti suderinami su originaliais.

[x]. Kovariacija, taip pat galimybė, kad palikuonis slepia protėvio eksportuotą komponentą, kartu su polimorfizmu, sukelia retą, bet labai rimtą tipo pažeidimo problemą.

[x].Šių pažeidimų galima išvengti naudojant: globalią analizę (tai nepraktiška), kovariacijos apribojimą iki fiksuotų tipų (tai prieštarauja Open-Closed principui), Catcall sprendimą, kuris neleidžia polimorfiniam taikiniui iškviesti paprogramę su kovariacija arba pasislėpti vaikas.

Bibliografiniai užrašai

Nemažai šios paskaitos medžiagos buvo pristatyta OOPSLA 95 ir TOOLS PACIFIC 95 forumų pranešimuose, taip pat buvo paskelbta. Iš straipsnio pasiskolinta nemažai apžvalginės medžiagos.

Buvo pristatyta automatinio tipo išvados sąvoka, kuri apibūdina funkcinės kalbos ML tipo išvados algoritmą. Ryšys tarp polimorfizmo ir tipo tikrinimo buvo tiriamas m.

Metodus, kaip padidinti dinamiškai įvestų kalbų kodo efektyvumą „Self“ kalbos kontekste, galite rasti.

Teorinį straipsnį apie programavimo kalbų tipus, turėjusius didelę įtaką specialistams, parašė Luca Cardelli ir Peteris Wegneris. Šis darbas, sukurtas lambda skaičiavimo pagrindu (žr.), buvo daugelio tolesnių tyrimų pagrindas. Prieš tai buvo kitas esminis Cardelli straipsnis.

ISE pamoka apima įvadą į polimorfizmo, kovariacijos ir vaiko slėpimosi kartu naudojimo problemas. Tinkamos analizės trūkumas pirmajame šios knygos leidime sukėlė daugybę kritinių diskusijų (pirmoji iš jų buvo Philippe'o Elincko komentarai jo bakalauro darbe „De la Conception-Programmation par Objets“, Memoire de licence, Universite Libre de Bruxelles (Belgija), 1988), išreikšta darbuose ir. Cooko darbe pateikiami keli pavyzdžiai, susiję su kovariacijos problema ir bandymai ją išspręsti. Standartiniais kovariantinių objektų parametrais pagrįstą sprendimą TOOLS EUROPE 1992 pasiūlė Franzas Weberis. Pateikiami tikslūs sistemos teisingumo, taip pat klasių teisingumo sąvokų apibrėžimai ir siūlomas sprendimas naudojant pilną sistemos analizę. Catcall sprendimas pirmą kartą buvo pasiūlytas m. taip pat žr.

Prisegimo sprendimas buvo pristatytas mano kalboje seminare TOOLS EUROPE 1994. Tačiau tuo metu nemačiau reikalo. inkaras- reklama ir susiję suderinamumo apribojimai. Paulas Duboisas ir Amiram Yehudai netruko pabrėžti, kad tokiomis sąlygomis kovariacijos problema išlieka. Jie, taip pat Reinhardtas Budde'as, Karlas-Heinzas Sylla, Kimas Waldenas ir Jamesas McKimas, pateikė daug pastabų, kurios turėjo esminės reikšmės darbui, paskatinusiam parašyti šią paskaitą.

Kovariacijos klausimams skirta daug literatūros. Ir rasite išsamią bibliografiją ir matematinių problemos aspektų apžvalgą. Nuorodų į internetinę medžiagą apie OOP tipo teoriją ir jų autorių tinklalapius rasite Laurento Dami puslapyje. Kovariacijos ir kontravariacijos sąvokos pasiskolintos iš kategorijų teorijos. Esame skolingi jų atsiradimui programinės įrangos spausdinimo kontekste Lucai Cardelliui, kuris pradėjo juos naudoti savo kalbose devintojo dešimtmečio pradžioje, bet spausdinti nenaudojo iki devintojo dešimtmečio pabaigos.

Standartiniais kintamaisiais pagrįsti metodai aprašyti , , .

Kontravariacija buvo įgyvendinta Sathero kalboje. Paaiškinimai pateikti .

Nors galimi tarpiniai variantai, čia yra du pagrindiniai metodai:

  • Dinaminis spausdinimas: palaukite, kol baigsis kiekvienas skambutis, tada priimkite sprendimą.
  • Statinis spausdinimas: atsižvelgiant į taisyklių rinkinį, iš šaltinio teksto nustatykite, ar vykdant galimi tipo pažeidimai. Sistema veikia, jei taisyklės garantuoja, kad klaidų nebus.

Šiuos terminus lengva paaiškinti: kada dinaminis spausdinimas tipo tikrinimas atliekamas, kai sistema veikia (dinamiškai) ir kada statinis spausdinimas teksto patikrinimas atliekamas statiškai (prieš vykdymą).

Statinis spausdinimas apima automatinį tikrinimą, paprastai priskiriamą kompiliatoriui. Dėl to turime paprastą apibrėžimą:

Apibrėžimas: statiškai spausdinama kalba

Į objektą orientuota kalba įvedama statiškai, jei ji pateikiama su nuoseklių, kompiliatoriaus patikrintų taisyklių rinkiniu, užtikrinančiu, kad sistemos vykdymas nesukels tipo pažeidimų.

Terminas " stiprus rašyti" ( stiprus). Tai atitinka apibrėžimo ultimatumą, reikalaujantį visiško tipo pažeidimo nebuvimo. Taip pat galima silpnas (silpnas) formos statinis spausdinimas, kurioje taisyklės pašalina tam tikrus pažeidimus, nepašalindami jų visiškai. Šia prasme kai kurios į objektus orientuotos kalbos yra statiškai silpnai įvestos. Kovosime už stipriausią tipizaciją.

B dinamiškai spausdintomis kalbomis, žinomas kaip nespausdintas, neturi tipo deklaracijų ir gali turėti bet kokias reikšmes, pridėtas prie objektų vykdymo metu. Statinis tipo tikrinimas juose negalimas.

Rašymo taisyklės

Mūsų OO žymėjimas yra statiškai įvestas. Jo tipo taisyklės buvo pristatytos ankstesnėse paskaitose ir susideda iš trijų paprastų reikalavimų.

  • Deklaruojant kiekvieną objektą ar funkciją, turi būti nurodytas jo tipas, pvz. acc: ACCOUNT. Kiekviena rutina turi 0 ar daugiau formalių argumentų, kurių tipas turi būti nurodytas, pvz.: put (x: G; i: INTEGER) .
  • Bet kuriame priskyrime x:= y ir bet kuriame paprogramės iškvietime, kai y yra tikrasis formalaus argumento x argumentas, šaltinio y tipas turi būti suderinamas su tikslo x tipu. Suderinamumo apibrėžimas grindžiamas paveldėjimu: B yra suderinamas su A, jei jis yra jo palikuonis – papildytas bendrųjų parametrų taisyklėmis (žr. „Paveldėjimo įvadas“).
  • Iškvietimas į x.f(arg) reikalauja, kad f būtų pagrindinės klasės komponentas tiksliniam x tipui, o f turi būti eksportuotas į klasę, kurioje skamba (žr. 14.3).

Realizmas

Nors statiškai spausdinamos kalbos apibrėžimas pateiktas gana tiksliai, jo neužtenka – kuriant spausdinimo taisykles reikalingi neformalūs kriterijai. Panagrinėkime du kraštutinius atvejus.

  • Visiškai teisinga kalba, kurioje kiekviena sintaksiškai teisinga sistema taip pat yra teisinga. Tipo deklaravimo taisyklių nereikia. Tokios kalbos egzistuoja (įsivaizduokite lenkišką užrašą sveikiesiems skaičiams pridėti ir atimti). Deja, jokia tikroji universali kalba neatitinka šio kriterijaus.
  • Visiškai neteisinga kalba, kurią lengva sukurti naudojant bet kurią esamą kalbą ir pridedant spausdinimo taisyklę, kuri leidžia bet koks sistema neteisinga. Pagal apibrėžimą kalba yra spausdinama: kadangi nėra sistemų, kurios laikytųsi taisyklių, jokia sistema nesukels tipo pažeidimo.

Galime sakyti, kad pirmojo tipo kalbos tinkamas, Bet nenaudingas, pastarasis gali būti naudingas, bet netinkamas.

Praktiškai mums reikia tipinės sistemos, kuri būtų tinkama naudoti ir naudinga: pakankamai galinga, kad atitiktų skaičiavimo poreikius, ir pakankamai patogi, kad nesukeltų mūsų sunkumų, susijusių su spausdinimo taisyklėmis.

Tarkim ta kalba tikroviškas, jei jis yra tinkamas naudoti ir naudingas praktikoje. Skirtingai nuo apibrėžimo statinis spausdinimas, kategoriškai atsakydamas į klausimą: " Ar kalba X įvedama statiškai?", realizmo apibrėžimas iš dalies yra subjektyvus.

Šioje paskaitoje įsitikinsime, kad mūsų siūloma žyma yra tikroviška.

Pesimizmas

Statinis spausdinimas savo prigimtimi veda prie „pesimistinės“ politikos. Bandymas tai užtikrinti visi skaičiavimai nesukelia nesėkmių, atmeta skaičiavimai gali baigtis be klaidų.

Apsvarstykite įprastą, neobjektyvią, į Paskalį panašią kalbą su įvairiais REAL ir INTEGER tipais. Apibūdinant n: INTEGER; r: Tikrasis operatorius n:= r bus atmestas kaip pažeidžiantis taisykles. Taigi, kompiliatorius atmes visus šiuos teiginius:

n: = 0,0 [A] n: = 1,0 [B] n: = -3,67 [C] n: = 3,67 - 3,67 [D]

Jei leisime juos vykdyti, pamatysime, kad [A] veiks visada, nes bet kuri skaičių sistema tiksliai atvaizduoja tikrąjį skaičių 0,0, kuris vienareikšmiškai paverčiamas 0 sveikųjų skaičių. [B] taip pat beveik neabejotinai veiks. Veiksmo [C] rezultatas nėra akivaizdus (ar norime gauti rezultatą suapvalindami ar atmesdami trupmeninę dalį?). [D] atliks darbą, kaip ir operatorius:

jei n^2< 0 then n:= 3.67 end [E]

kuri apima nepasiekiamą priskyrimą (n^2 yra n kvadratas). Pakeitus n^2 į n, teisingą rezultatą pateiks tik eilė paleidimų. Jei n priskirsite didelę realią reikšmę, kuri negali būti atvaizduojama kaip sveikasis skaičius, tai bus nesėkminga.

IN spausdintomis kalbomis visi šie pavyzdžiai (veikiantys, neveikiantys, kartais veikiantys) yra negailestingai traktuojami kaip tipo aprašymo taisyklių pažeidimai ir bet kurio kompiliatoriaus atmetami.

Klausimas ne mes ar mes pesimistai, bet faktas yra kiek galime sau leisti būti pesimistai. Grįžkime prie realizmo reikalavimo: jei tipo taisyklės yra tokios pesimistinės, kad trukdo rašyti skaičiavimus, jas atmesime. Bet jei tipo saugumas pasiekiamas už nedidelę išraiškos galią, mes juos priimsime. Pavyzdžiui, kūrimo aplinkoje, kurioje pateikiamos apvalios ir sutrumpintos funkcijos, operatorius n:= r laikomas neteisingu, nes jis verčia aiškiai įrašyti realaus skaičiaus konvertavimą į sveikąjį skaičių, o ne naudoti numatytąsias dviprasmiškas konversijas.

Statinis spausdinimas: kaip ir kodėl

Nors nauda statinis spausdinimas yra akivaizdūs, gerai apie juos pakalbėti dar kartą.

Privalumai

Naudojimo priežastys statinis spausdinimas objektų technologijoje išvardinome paskaitos pradžioje. Tai yra patikimumas, supratimo paprastumas ir efektyvumas.

Patikimumas atsiranda dėl klaidų, kurios kitu atveju galėtų pasireikšti tik eksploatacijos metu, ir tik kai kuriais atvejais. Pirmoji iš taisyklių, kuri verčia deklaruoti objektus, taip pat funkcijas, įveda programos teksto pertekliškumą, o tai leidžia kompiliatoriui, naudojant kitas dvi taisykles, aptikti neatitikimus tarp numatyto ir faktinio objektų, komponentų ir komponentų naudojimo. posakius.

Ankstyvas klaidų aptikimas taip pat svarbus, nes kuo ilgiau laukiame, kol jas surasime, tuo didės taisymo išlaidos. Šią savybę, intuityviai suprantamą visiems profesionaliems programuotojams, kiekybiškai patvirtina gerai žinomi Boehm darbai. Koregavimo sąnaudų priklausomybė nuo klaidų nustatymo laiko parodyta diagramoje, pagrįstoje daugelio didelių pramoninių projektų ir eksperimentų, atliktų su nedideliu kontroliuojamu projektu, duomenimis:


Ryžiai. 17.1.

Skaitomumas arba Lengva suprasti(skaitomumas) turi savo privalumų. Visuose šios knygos pavyzdžiuose tipo atsiradimas objekte suteikia skaitytojui informacijos apie jo paskirtį. Priežiūros etapo metu itin svarbu skaitomumas.

Pagaliau, efektyvumą gali nustatyti objektų technologijos sėkmę ar nesėkmę praktikoje. Nesant statinis spausdinimas x.f(arg) užbaigimas gali užtrukti kiek laiko. To priežastis yra ta, kad vykdymo metu, jei f nėra rastas pagrindinėje tikslo x klasėje, paieška bus tęsiama jo palikuoniuose, o tai yra neefektyvumo receptas. Galite palengvinti problemą pagerindami komponento paiešką hierarchijoje. „Self“ autoriai įdėjo daug darbo, kad sukurtų geriausią kodą dinamiškai spausdinamai kalbai. Bet tiksliai statinis spausdinimas leido tokiam į objektą orientuotam produktui priartėti prie tradicinės programinės įrangos arba prilygti jo efektyvumui.

Raktas į statinis spausdinimas ar jau išsakyta mintis, kad konstravimo x.f (arg) kodą generuojantis kompiliatorius žino x tipą. Dėl polimorfizmo nėra galimybės vienareikšmiškai nustatyti tinkamos f komponento versijos. Tačiau deklaracija susiaurina galimų tipų rinkinį, todėl kompiliatorius gali sudaryti lentelę, suteikiančią prieigą prie teisingo f su minimaliomis papildomomis sąnaudomis - su ribota konstanta prieigos sunkumas. Atlikti papildomi optimizavimai statinis įrišimas Ir įdėklas- taip pat tapo lengviau dėka statinis spausdinimas, visiškai pašalinant išlaidas ten, kur jos taikomos.

Dinaminio spausdinimo atvejis

Nepaisant viso to, dinaminis spausdinimas nepraranda savo šalininkų, ypač tarp „Smalltalk“ programuotojų. Jų argumentai pirmiausia pagrįsti aukščiau aptartu realizmu. Jie tuo įsitikinę statinis spausdinimas per daug riboja juos, neleidžia laisvai reikšti savo kūrybinių idėjų, kartais tai vadina „skaistybės diržu“.

Galima sutikti su tokiais samprotavimais, tačiau tik statiškai įvestoms kalboms, kurios nepalaiko daugelio funkcijų. Verta paminėti, kad visos sąvokos, susijusios su tipo sąvoka ir pristatytos ankstesnėse paskaitose, yra būtinos - bet kurios iš jų atmetimas yra kupinas rimtų apribojimų, o jų įvedimas, priešingai, suteikia mūsų veiksmams lankstumo ir suteikia mums galimybė visiškai mėgautis praktiškumu statinis spausdinimas.

Tipavimas: sėkmės komponentai

Kokie yra realistinio mechanizmai statinis spausdinimas? Visi jie buvo pristatyti ankstesnėse paskaitose, todėl galime tik trumpai prisiminti. Jų išvardijimas kartu parodo jų asociacijos nuoseklumą ir galią.

Mūsų tipo sistema yra visiškai pagrįsta koncepcija klasė. Netgi pagrindiniai tipai, tokie kaip INTEGER, yra klasės, o tai reiškia, kad mums nereikia specialių taisyklių iš anksto apibrėžtiems tipams apibūdinti. (Čia mūsų žymėjimas skiriasi nuo „hibridinių“ kalbų, tokių kaip „Object Pascal“, „Java“ ir „C++“, kurios sujungia senesnių kalbų tipų sistemas su klasės objektų technologija.)

Išplėsti tipai Suteikite mums daugiau lankstumo leisdami tipus, kurių reikšmės žymi objektus, ir tipus, kurių reikšmės žymi nuorodas.

Lemiamas žodis kuriant lanksčią tipo sistemą priklauso paveldėjimo ir susijusi sąvoka suderinamumas. Taip įveikiamas pagrindinis klasikinių kalbų, tokių kaip Pascal ir Ada, apribojimas, kai x:= y operatorius reikalauja, kad x ir y tipai būtų vienodi. Ši taisyklė per griežta: ji draudžia naudoti objektus, galinčius atstovauti susijusių tipų objektus (SAVINGS_ACCOUNT ir CHECKING_ACCOUNT ). Paveldėdami tik reikalaujame tipo suderinamumas y su x tipu, pavyzdžiui, x yra ACCOUNT tipo, y yra SAVINGS_ACCOUNT, o antroji klasė yra pirmosios palikuonis.

Praktiškai statiškai įvestą kalbą reikia palaikyti daugybinis paveldėjimas. Esminiai kaltinimai žinomi statinis spausdinimas yra tai, kad ji neleidžia objektų interpretuoti skirtingai. Taigi, DOKUMENTO objektas gali būti perduotas tinkle, todėl jam reikia komponentų, susietų su MESSAGE tipu. Tačiau ši kritika galioja tik ribotoms kalboms vienkartinis paveldėjimas.


Ryžiai. 17.2.

Universalumas reikalingi, pavyzdžiui, norint apibūdinti lanksčias, bet saugias konteinerio duomenų struktūras (pvz klasių SĄRAŠAS [G] ...). Be šio mechanizmo, statinis spausdinimas Reikėtų deklaruoti skirtingas klases sąrašams, kurie skiriasi elementų tipu.

Kai kuriais atvejais reikalingas universalumas riba, kuri leidžia naudoti operacijas, taikomas tik bendrojo tipo objektams. Jei bendroji klasė SORTABLE_LIST palaiko rūšiavimą, reikia, kad G tipo objektai, kur G yra bendrasis parametras, turėtų palyginimo operatorių. Tai pasiekiama susiejant su G klasę, kuri nurodo bendrąjį apribojimą – PALYGINAMAS:

klasė SORTABLE_LIST...

Bet koks faktinis bendrasis SORTABLE_LIST parametras turi būti COMPARABLE klasės, turinčios reikiamą komponentą, palikuonis.

Kitas privalomas mechanizmas yra paskyrimo bandymas- organizuoja prieigą prie tų objektų, kurių tipo programinė įranga nekontroliuoja. Jei y yra duomenų bazės objektas arba tinkle nuskaitytas objektas, tada operatorius x ?= y priskirs x y reikšmę, jei y yra suderinamo tipo, arba, jei ne, duos x reikšmę Void .

pareiškimai Susietos, kaip dizaino pagal sutartį idėjos dalis, su klasėmis ir jų komponentais išankstinių sąlygų, posąlygų ir klasių invariantų pavidalu, leidžia apibūdinti neapimamus semantinius apribojimus. tipo specifikacija. Tokios kalbos kaip Pascal ir Ada turi diapazono tipus, kurie gali apriboti objektų reikšmes iki, pavyzdžiui, nuo 10 iki 20, bet jūs negalėsite užtikrinti, kad i būtų neigiamas, nes visada yra dvigubai didesnis už j reikšmę. Į pagalbą ateina klasės invariantai, sukurti taip, kad tiksliai atspindėtų taikomus apribojimus, kad ir kokie sudėtingi jie būtų.

Prisegti skelbimai būtinos, kad praktiškai išvengtų kodo dubliavimo lavinų. Skelbdamas y: kaip x, gausite garantiją, kad y pasikeis po bet kokių pakartotinių x tipo deklaracijų palikuonyje. Be šio mechanizmo kūrėjai nuolat iš naujo deklaruotų, bandydami išlaikyti skirtingų tipų nuoseklumą.

Lipnios deklaracijos yra ypatingas paskutinio mums reikalingo kalbos mechanizmo atvejis – kovariacija, kurį išsamiai aptarsime vėliau.

Kuriant programinės įrangos sistemas, iš tikrųjų būtina dar viena savybė, būdinga pačiai kūrimo aplinkai - greitas laipsniškas perkompiliavimas. Kai rašote arba modifikuojate sistemą, norite kuo greičiau pamatyti pakeitimų poveikį. At statinis spausdinimas turėtumėte duoti kompiliatoriui laiko dar kartą patikrinti tipus. Dėl tradicinių kompiliavimo veiksmų reikia iš naujo išversti visą sistemą (ir jos asamblėjos), ir šis procesas gali būti skausmingai ilgas, ypač pereinant prie didesnio masto sistemų. Šis reiškinys tapo argumentu už interpretacinis sistemos, pvz., ankstyvosios Lisp arba Smalltalk aplinkos, paleido sistemą be jokio apdorojimo ir be tipo tikrinimo. Šis argumentas dabar pamirštas. Geras šiuolaikinis kompiliatorius nustato, kaip pasikeitė kodas nuo paskutinio kompiliavimo, ir apdoroja tik tuos pakeitimus, kuriuos rado.

„Ar kūdikis spausdinamas“?

Mūsų tikslas - griežtas statinis spausdinimas. Štai kodėl turime vengti bet kokių spragų mūsų „žaidime pagal taisykles“, bent jau tiksliai jas identifikuoti, jei tokių yra.

Dažniausia statinio spraga spausdintomis kalbomis yra transformacijų, kurios keičia subjekto tipą, buvimas. C ir jo išvestinėse kalbose jie vadinami „tipo liejimu“ arba liejimu. Žymėjimas (OTHER_TYPE) x nurodo, kad reikšmę x kompiliatorius traktuoja kaip OTHER_TYPE tipą, atsižvelgiant į tam tikrus galimų tipų apribojimus.

Tokie mechanizmai apeina tipo tikrinimo apribojimus. Liejimas yra įprastas programuojant C, įskaitant ANSI C dialektą. Net C++ kalboje, tipo liejimas, nors ir rečiau, išlieka įprastas ir galbūt būtinas.

Kad būtų laikomasi taisyklių statinis spausdinimas ne taip paprasta, jei juos galima bet kada apeiti liejant.

Rašymas ir susiejimas

Nors kaip šios knygos skaitytojas tikriausiai atskirsite statinį spausdinimą nuo statinio privalomas, yra žmonių, kurie to negali padaryti. Tai iš dalies gali būti dėl Smalltalk kalbos, kuri pasisako, įtakos dinamiškas požiūris abiem problemoms ir gali susidaryti klaidinga nuomonė, kad jų sprendimas yra tas pats. (Savo knygoje teigiame, kad norint sukurti patikimas ir lanksčias programas, pageidautina derinti statinį spausdinimą ir dinaminį įrišimą.)

Tiek spausdinimas, tiek įrišimas susijęs su pagrindinės konstrukcijos x.f (arg) semantika, tačiau atsakykite į du skirtingus klausimus:

Rašymas ir susiejimas

  • Rašymo klausimas: kada mes turime tiksliai žinoti, kad vykdymo metu bus atlikta operacija, atitinkanti f, taikoma objektui, prijungtam prie objekto x (su parametru arg )?
  • Susiejimo klausimas: Kada turėtume žinoti, kokią operaciją inicijuoja duotas skambutis?

Įvedant tekstą atsakoma į prieinamumo klausimą mažiausiai vienas operacijas, už atranką atsakingas privalomas būtina.

Objekto požiūriu:

  • problema, kuri kyla spausdinant, yra susijusi su polimorfizmas: nuo x vykdymo metu gali žymėti kelių skirtingų tipų objektus, turime būti tikri, kad operacija, vaizduojanti f, yra prieinama kiekvienu iš šių atvejų;
  • sukelta įrišimo problema pakartotiniai pranešimai: Kadangi klasė gali pakeisti paveldėtus komponentus, gali būti dvi ar daugiau operacijų, kurios nurodo, kad tam tikrame iškvietime atstovauja f.

Abi problemos gali būti išspręstos tiek dinamiškai, tiek statiškai. Esamos kalbos pateikia visus keturis sprendimus.

O dinaminis įrišimas įkūnytas šioje knygoje siūlomoje žymėjime.

Atkreipkime dėmesį į C++ kalbos, kuri palaiko statinį spausdinimą, unikalumą, nors ir nėra griežta dėl tipo liejimo, statinis susiejimas(numatytasis), dinaminis susiejimas, kai aiškiai nurodomas virtualus ( virtualus) skelbimai.

Pasirinkimo priežastis statinis spausdinimas o dinaminis susiejimas yra akivaizdus. Pirmas klausimas yra toks: „Kada sužinosime, kad komponentai egzistuoja? - siūlo statinį atsakymą: " Kuo anksčiau, tuo geriau", o tai reiškia: kompiliavimo metu. Antrasis klausimas: "Kokį komponentą turėčiau naudoti?" reikia dinamiško atsakymo: " ta, kurios tau reikia", - atitinkamas dinaminis tipas objektas, nustatytas vykdymo metu. Tai vienintelis priimtinas sprendimas, jei statinis ir dinaminis įrišimas duoda skirtingus rezultatus.

At statinis spausdinimas Kompiliatorius neatmes iškvietimo, jei galima garantuoti, kad kai programa bus vykdoma, prie objekto my_aircraft bus prijungtas objektas, kuris ateina su komponentu, atitinkančiu Low_landing_gear . Pagrindinė garantijų gavimo technika yra paprasta: privaloma my_aircraft deklaracija reikalauja, kad jo tipo bazinėje klasėje būtų toks komponentas. Todėl mano_orlaivis negali būti paskelbtas kaip ORLAIVA, nes pastarasis neturi žemesnės_tūpimo pavaros tame lygyje; sraigtasparniai, bent jau mūsų pavyzdyje, nemoka nuleisti važiuoklės. Jei subjektą paskelbsime kaip LĖKTUVA, - klasė, kurioje yra reikalingas komponentas - viskas bus gerai.

Dinaminis spausdinimas„Smalltalk“ stiliuje reikia laukti skambučio ir jo vykdymo metu patikrinti, ar nėra reikiamo komponento. Toks elgesys įmanomas prototipams ir eksperimentinėms kūrimui, tačiau nepriimtinas pramoninėms sistemoms – skrydžio metu per vėlu paklausti, ar turite važiuoklę.

Šiame straipsnyje paaiškinami skirtumai tarp statinio tipo ir dinamiškai įvedamų kalbų, nagrinėjamos „stipraus“ ir „silpno“ spausdinimo sąvokos ir palyginama spausdinimo sistemų galia skirtingomis kalbomis. Pastaruoju metu programuojant aiškiai judama link griežtesnių ir galingesnių spausdinimo sistemų, todėl svarbu suprasti, ką tai reiškia. mes kalbame apie kai kalbame apie tipus ir tipizavimą.



Tipas yra galimų reikšmių rinkinys. Sveikasis skaičius gali turėti reikšmes 0, 1, 2, 3 ir pan. Būlio reikšmė gali būti teisinga arba klaidinga. Galite sugalvoti savo tipą, pavyzdžiui, tipą „High Five“, kuriame galimos reikšmės yra „aukšta“ ir „5“, ir nieko daugiau. Tai ne eilutė ar skaičius, tai naujas, atskiras tipas.


Statiškai įvestos kalbos riboja kintamųjų tipus: programavimo kalba gali žinoti, pavyzdžiui, kad x yra sveikasis skaičius. Tokiu atveju programuotojui draudžiama daryti x = true, tai bus neteisingas kodas. Kompiliatorius atsisakys jį kompiliuoti, todėl mes net negalėsime paleisti kodo. Kita statiškai spausdinama kalba gali turėti skirtingas išraiškos galimybes ir nė viena iš populiarių tipų sistemų negali išreikšti mūsų HighFive tipo (tačiau daugelis gali išreikšti kitas, sudėtingesnes idėjas).


Dinamiškai įvestos kalbos žymi reikšmes tipais: kalba žino, kad 1 yra sveikas skaičius, 2 yra sveikas skaičius, bet negali žinoti, kad kintamajame x visada yra sveikasis skaičius.


Kalbos vykdymo laikas tikrina šias etiketes skirtingais laiko momentais. Jei bandysime pridėti dvi reikšmes, ji gali patikrinti, ar tai skaičiai, eilutės ar masyvai. Tada jis pridės šias reikšmes, suklijuos jas arba išmes klaidą, priklausomai nuo tipo.

Statiškai įvestos kalbos

Statinės kalbos tikrina programos tipus kompiliavimo metu, prieš paleidžiant programą. Bet kuri programa, kurios tipai pažeidžia kalbos taisykles, laikoma neteisinga. Pavyzdžiui, dauguma statinių kalbų atmes posakį „a“ + 1 (C yra šios taisyklės išimtis). Kompiliatorius žino, kad "a" yra eilutė, o 1 yra sveikas skaičius, o + veikia tik tada, kai kairioji ir dešinė pusės yra to paties tipo. Taigi jam nereikia paleisti programos, kad suprastų, jog yra problema. Kiekviena išraiška statiškai įvestoje kalboje yra konkretaus tipo, kurį galima nustatyti nepaleidžiant kodo.


Daugeliui statiškai įvestų kalbų reikalingas tipo žymėjimas. Java funkcija public int add(int x, int y) paima du sveikuosius skaičius ir grąžina trečią sveikąjį skaičių. Kitos statiškai įvestos kalbos tipą gali nustatyti automatiškai. Ta pati pridėjimo funkcija Haskell atrodo taip: pridėti x y = x + y . Kalbai tipų nenurodome, bet ji gali juos išsiaiškinti pati, nes žino, kad + veikia tik su skaičiais, todėl x ir y turi būti skaičiai, todėl funkcija pridėti kaip argumentus paima du skaičius.


Tai nesumažina tipo sistemos „statiškumo“. Haskell tipo sistema garsėja tuo, kad yra statiška, griežta ir galinga, o Haskell visuose šiuose frontuose lenkia Java.

Dinamiškai įvestos kalbos

Dinamiškai įvestoms kalboms tipo nurodyti nereikia, tačiau jos pačios jo neapibrėžia. Kintamųjų tipai nežinomi, kol jie neturi konkrečių verčių paleidimo metu. Pavyzdžiui, funkcija Python


def f(x, y): grąžinti x + y

gali pridėti du sveikuosius skaičius, sujungti eilutes, sąrašus ir pan., ir mes negalime suprasti, kas tiksliai vyksta, kol nepaleidžiame programos. Gali būti, kad funkcija f tam tikru momentu bus iškviesta dviem eilutėmis, o kitu metu – dviem skaičiais. Šiuo atveju x ir y skirtingu laiku bus skirtingų tipų reikšmės. Štai kodėl teigiama, kad dinaminių kalbų reikšmės turi tipą, o kintamieji ir funkcijos neturi. Reikšmė 1 tikrai yra sveikas skaičius, bet x ir y gali būti bet koks.

Palyginimas

Dauguma dinamiškų kalbų išmes klaidą, jei tipai bus naudojami neteisingai („JavaScript“ yra pastebima išimtis; ji bando grąžinti bet kokios išraiškos reikšmę, net jei ji neturi prasmės). Naudojant dinamiškai įvestas kalbas, gamybinėje aplinkoje gali įvykti net tokia paprasta klaida, kaip „a“ + 1. Statinės kalbos užkerta kelią tokioms klaidoms, tačiau, žinoma, prevencijos laipsnis priklauso nuo tipo sistemos stiprumo.


Statinės ir dinaminės kalbos yra pagrįstos iš esmės skirtingomis idėjomis apie programos teisingumą. Dinaminėje kalboje "a" + 1 yra tinkama programa: kodas bus paleistas ir vykdymo aplinkoje pasirodys klaida. Tačiau daugumoje statiškai įvestų kalbų išraiška „a“ + 1 yra ne programa: Jis nebus kompiliuojamas ir nebus paleistas. Tai neteisingas kodas, kaip ir atsitiktinių simbolių rinkinys!&%^@*&%^@* yra neteisingas kodas. Ši papildoma teisingumo ir neteisingumo samprata neturi atitikmens dinaminėse kalbose.

Stiprus ir silpnas spausdinimas

Sąvokos „stiprus“ ir „silpnas“ yra labai dviprasmiškos. Štai keletas jų naudojimo pavyzdžių:

    Kartais „stiprus“ reiškia „statinis“.
    Tai paprasta, bet geriau vartoti terminą „statinis“, nes dauguma žmonių jį vartoja ir supranta.

    Kartais „stiprus“ reiškia „neatlieka numanomo tipo konvertavimo“.
    Pavyzdžiui, „JavaScript“ leidžia rašyti „a“ + 1, o tai gali būti vadinama „silpnu spausdinimu“. Tačiau beveik visose kalbose yra tam tikro lygio netiesioginis konvertavimas, leidžiantis automatiškai konvertuoti sveikuosius skaičius į slankiojo kablelio skaičius, pvz., 1 + 1,1. Tiesą sakant, dauguma žmonių vartoja žodį „stiprus“, norėdami apibrėžti ribą tarp priimtino ir nepriimtino atsivertimo. Nėra visuotinai priimtos ribos, jos visos yra netikslios ir priklauso nuo konkretaus žmogaus nuomonės.

    Kartais „stiprus“ reiškia, kad neįmanoma apeiti griežtų kalbos spausdinimo taisyklių.

  • Kartais „stiprus“ reiškia saugų atmintį.
    C yra nesaugios atminties kalbos pavyzdys. Jei xs yra keturių skaičių masyvas, tada C mielai vykdys xs arba xs , grąžindamas tam tikrą reikšmę iš atminties iškart už xs .

Sustokime. Štai kaip kai kurios kalbos atitinka šiuos apibrėžimus. Kaip matote, tik Haskell yra nuolat „stiprus“ visais atžvilgiais. Dauguma kalbų nėra tokios aiškios.



(Stulpelyje „Numanomos konversijos“ esantis „Kada kaip“ reiškia, kad skirstymas į stiprias ir silpnas priklauso nuo to, kokias konversijas laikome priimtinomis).


Dažnai terminai „stiprus“ ir „silpnas“ reiškia miglotą įvairių aukščiau pateiktų apibrėžimų ir kitų čia nepateiktų apibrėžimų derinį. Dėl visos šios painiavos žodžiai „stiprus“ ir „silpnas“ tampa beveik beprasmiai. Kai norite vartoti šiuos terminus, geriau apibūdinkite, kas tiksliai turi omenyje. Pavyzdžiui, galite pasakyti, kad „JavaScript grąžina reikšmę, kai pridedama eilutė su skaičiumi, bet Python pateikia klaidą“. Tokiu atveju neeikvosime savo energijos bandydami susitarti dėl kelių žodžio „stiprus“ reikšmių. Arba dar blogiau: baigsime neišspręstus nesusipratimus dėl terminijos.


Dažniausiai internete vartojamos sąvokos „stiprus“ ir „silpnas“ yra neaiškios ir menkai apibrėžtos konkrečių asmenų nuomonės. Jie vartojami kalbai vadinti „bloga“ arba „gera“, ir ši nuomonė virsta techniniu žargonu.



Stiprus spausdinimas: tipo sistema, kuri man patinka ir kuri man patinka.

Silpnas spausdinimas: tipo sistema, kuri mane trikdo arba kuri man netinka.

Laipsniškas spausdinimas

Ar galima prie dinaminių kalbų pridėti statinių tipų? Kai kuriais atvejais – taip. Kitose tai sunku arba neįmanoma. Akivaizdžiausia problema – eval ir kitos panašios dinaminių kalbų savybės. Atlikus 1 + eval("2") Python, gaunama 3. Bet ką duoda 1 + eval(read_from_the_network())? Tai priklauso nuo to, kas yra internete vykdymo metu. Jei gauname skaičių, tada išraiška yra teisinga. Jei tai eilutė, tada ne. Prieš paleisdami jokiu būdu negalima žinoti, todėl neįmanoma išanalizuoti tipo statiškai.


Nepatenkinamas sprendimas praktikoje yra nustatyti reiškinį eval() į tipą Any, kuris yra panašus į Object kai kuriose objektinio programavimo kalbose arba sąsają () programoje Go: tai tipas, kurį galima patenkinti bet kokia reikšme.


Bet tipo reikšmės yra neribotos, todėl tipinės sistemos galimybė mums padėti su eval kodu išnyksta. Kalbos, turinčios ir eval, ir tipo sistemą, turi atsisakyti tipo saugos, kai naudojama eval.


Kai kuriose kalbose yra pasirenkamas arba laipsniškas spausdinimas: pagal numatytuosius nustatymus jos yra dinaminės, tačiau leidžia pridėti keletą statinių komentarų. Python neseniai pridėjo pasirenkamus tipus; „TypeScript“ yra „JavaScript“ superrinkinys, turintis pasirenkamų tipų; „Flow“ atlieka statinę seno gero „JavaScript“ kodo analizę.


Šios kalbos suteikia tam tikrų statinio spausdinimo pranašumų, tačiau jos niekada nesuteiks absoliučios tikrai statiškų kalbų garantijų. Kai kurios funkcijos bus įvestos statiškai, o kai kurios – dinamiškai. Programuotojas visada turėtų žinoti skirtumą ir būti atsargus.

Statiškai įvesto kodo sudarymas

Kompiliuojant statiškai įvestą kodą, pirmiausia patikrinama sintaksė, kaip ir bet kuriame kompiliatoriuje. Tada tikrinami tipai. Tai reiškia, kad statinė kalba pirmiausia gali skųstis viena sintaksės klaida, o ją ištaisius – 100 spausdinimo klaidų. Sintaksės klaidos taisymas nesukūrė tų 100 spausdinimo klaidų. Kompiliatorius tiesiog negalėjo aptikti tipo klaidų, kol sintaksė nebuvo ištaisyta.


Statinių kalbų kompiliatoriai paprastai gali generuoti greitesnį kodą nei dinaminių kalbų kompiliatoriai. Pavyzdžiui, jei kompiliatorius žino, kad funkcija pridėti priima sveikuosius skaičius, jis gali naudoti procesoriaus savąją ADD instrukciją. Dinaminė kalba tikrins tipą vykdymo metu, pasirinkdama iš daugybės pridėjimo funkcijų, priklausomai nuo tipų (pridėti sveikuosius skaičius ar slankiuosius skaičius, arba sujungti eilutes, o gal sąrašus?) arba nuspręsti, kad įvyko klaida ir tipai nesutampa. . Visi šie patikrinimai užtrunka. Dinaminėse kalbose optimizavimui naudojami įvairūs gudrybės, pvz., JIT kompiliavimas (just-in-time), kai kodas yra perkompiliuojamas vykdymo metu, gavus visą reikiamą tipo informaciją. Tačiau jokia dinamiška kalba negali prilygti tvarkingai užrašyto statinio kodo greičiui tokia kalba kaip Rust.

Statinių ir dinaminių tipų argumentai

Statinio tipo sistemos šalininkai atkreipia dėmesį, kad be tipo sistemos paprastos klaidos gali sukelti problemų gamyboje. Tai, žinoma, tiesa. Kiekvienas, vartojęs dinamišką kalbą, tai patyrė iš pirmų rankų.


Dinaminių kalbų šalininkai pabrėžia, kad tokiomis kalbomis kodą rašyti lengviau. Tai neabejotinai galioja kai kurioms kodų rūšims, kurias retkarčiais rašome, pavyzdžiui, eval kodui. Tai prieštaringas sprendimas įprastam darbui, ir čia prasminga prisiminti neaiškų žodį „lengvas“. Rich Hickey atliko puikų darbą kalbėdamas apie žodį „lengvas“ ir jo ryšį su žodžiu „paprastas“. Peržiūrėję šį reportažą suprasite, kad nelengva teisingai vartoti žodį „lengva“. Saugokitės „lengvo“.


Privalumai ir trūkumai statinio ir dinamines sistemas spausdinimo tekstai vis dar prastai suprantami, tačiau jie neabejotinai priklauso nuo kalbos ir konkrečios sprendžiamos problemos.


„JavaScript“ bando tęsti, net jei tai reiškia beprasmę konversiją (pvz., „a“ + 1, dėl kurio gaunama „a1“). Kita vertus, „Python“ stengiasi būti konservatyvus ir dažnai pateikia klaidas, kaip „a“ + 1 atveju.


Yra įvairių požiūrių su skirtinguose lygiuose saugumas, tačiau „Python“ ir „JavaScript“ yra dinamiškai įvestos kalbos.



Haskell neleis jums pridėti sveikojo skaičiaus ir slankiojo skaičiaus be aiškios konversijos. C ir Haskell yra statiniai, nepaisant tokių didelių skirtumų.


Yra daug dinaminių ir statinių kalbų variantų. Bet koks bendras teiginys, pavyzdžiui, „statinės kalbos yra geresnės už dinamines kalbas, kai kalbama apie X“, beveik garantuotai bus nesąmonė. Tai gali būti tiesa, kai kalbama apie konkrečias kalbas, bet tada geriau pasakyti: „Haskell yra geresnis už Python, kai kalbama apie X“.

Statinio spausdinimo sistemų įvairovė

Pažvelkime į du garsius statiškai įvestų kalbų pavyzdžius: Go ir Haskell. „Go“ spausdinimo sistema neturi bendrinių tipų, tipų su „parametrais“ iš kitų tipų. Pavyzdžiui, galime sukurti savo „MyList“ sąrašų tipą, kuriame galima saugoti bet kokius mums reikalingus duomenis. Mes norime turėti galimybę sukurti sveikųjų skaičių MyList, eilučių MyList ir pan. šaltinis Mano sąrašas. Kompiliatorius turi stebėti spausdinimą: jei yra sveikųjų skaičių MyList ir mes netyčia įtraukiame ten eilutę, tada kompiliatorius turi atmesti programą.


„Go“ buvo specialiai sukurtas taip, kad nebūtų galima apibrėžti tokių tipų kaip „MyList“. Geriausia, ką galima padaryti, yra sukurti „tuščių sąsajų“ MyList: „MyList“ gali turėti objektų, tačiau kompiliatorius tiesiog nežino jų tipo. Kai gauname objektus iš „MyList“, turime nurodyti kompiliatoriui jų tipą. Jei sakome „gaunu eilutę“, bet iš tikrųjų reikšmė yra skaičius, įvyks vykdymo klaida, kaip ir dinaminių kalbų atveju.


„Go“ taip pat neturi daugelio kitų funkcijų, esančių šiuolaikinėse statiškai įvestose kalbose (ar net kai kuriose aštuntojo dešimtmečio sistemose). „Go“ kūrėjai turėjo savo priežasčių tokius sprendimus priimti, tačiau pašalinių žmonių nuomonė šiuo klausimu kartais gali skambėti griežtai.


Dabar palyginkime su Haskell, kuris turi labai galingą tipo sistemą. Jei nustatote tipą į „MyList“, „numerių sąrašo“ tipas yra tiesiog „MyList Integer“ . Haskell neleis mums netyčia pridėti eilutės į sąrašą ir užtikrins, kad į eilutės kintamąjį neįdėtume elemento iš sąrašo.


Haskell gali išreikšti daug sudėtingesnes idėjas tiesiogiai su tipais. Pavyzdžiui, Num a => MyList a reiškia "Mano reikšmių sąrašas, priklausantis to paties tipo skaičiams". Tai gali būti sveikųjų skaičių, slankiųjų ar slankiųjų skaičių sąrašas dešimtainiai skaičiai fiksuotas tikslumas, bet tai tikrai niekada nebus eilučių sąrašas, kuris tikrinamas kompiliavimo metu.


Galite parašyti pridėjimo funkciją, kuri veikia su bet kokiu skaičių tipu. Šios funkcijos tipas bus Num a => (a -> a -> a) . Tai reiškia:

  • a gali būti bet koks skaitinis tipas (Num a =>).
  • Funkcija paima du a tipo argumentus ir grąžina tipą a (a -> a -> a).

Paskutinis pavyzdys. Jei funkcijos tipas yra String -> String , tada ji priima eilutę ir grąžina eilutę. Bet jei tai String -> IO String , tada ji taip pat atlieka tam tikrą įvesties / išvesties funkciją. Tai gali būti prieiga prie disko, prieiga prie tinklo, skaitymas iš terminalo ir pan.


Jei funkcija turi tipą Nr IO, tada žinome, kad jis neatlieka jokių I/O operacijų. Pavyzdžiui, žiniatinklio programoje galite pasakyti, ar funkcija modifikuoja duomenų bazę, tiesiog žiūrėdami į jos tipą. Jokia dinamiška ir beveik jokios statinės kalbos to negali padaryti. Tai kalbų su galingiausiomis spausdinimo sistemomis ypatybė.


Daugumoje kalbų turėtume naršyti per funkciją ir visas iš ten iškviečiamas funkcijas ir t. t., bandydami rasti ką nors, kas pakeistų duomenų bazę. Tai varginantis procesas ir lengva padaryti klaidų. O Haskell tipo sistema į šį klausimą gali atsakyti paprastai ir patikimai.


Palyginkite šią galią su „Go“, kuri negali išreikšti paprastos „MyList“ idėjos, jau nekalbant apie „funkciją, kuri turi du argumentus, tiek skaitinius, tiek to paties tipo, ir kuri atlieka I/O“.


„Go“ metodas leidžia lengviau rašyti „Go“ programavimo įrankius (ypač kompiliatoriaus įgyvendinimas gali būti paprastas). Be to, reikia išmokti mažiau sąvokų. Kaip šios naudos palyginti su reikšmingais apribojimais, yra subjektyvus klausimas. Tačiau negalima ginčytis, kad Haskell yra sunkiau išmokstamas nei Go ir kad Haskell tipo sistema yra daug galingesnė ir kad Haskell kompiliuodamas gali užkirsti kelią daugybei kitų klaidų tipų.


„Go“ ir „Haskell“ yra tokios skirtingos kalbos, kad jų sugrupavimas į tą pačią „statinių kalbų“ klasę gali būti klaidinantis, net jei terminas vartojamas teisingai. Lyginant praktinius saugumo pranašumus, Go yra artimesnis dinamiškoms kalboms nei Haskell.


Kita vertus, kai kurios dinamiškos kalbos yra saugesnės nei kai kurios statinės kalbos. (Python paprastai laikomas daug saugesniu nei C). Jei norite apibendrinti statines ar dinamines kalbas kaip grupes, nepamirškite apie tai didžiulis skaičius kalbų skirtumai.

Konkretūs spausdinimo sistemų galimybių skirtumų pavyzdžiai

Galingesnės spausdinimo sistemos gali nurodyti apribojimus mažesniais lygiais. Štai keli pavyzdžiai, tačiau nesijaudinkite dėl jų, jei sintaksė neaiški.


„Go“ galite pasakyti „pridėjimo funkcija užima du sveikuosius skaičius ir grąžina sveikąjį skaičių“:


func add(x int, y int) int ( return x + y )

Haskell galite pasakyti „funkcija trunka bet koks skaitinį tipą ir grąžina to paties tipo skaičių":


f:: Skaičius a => a -> a -> a pridėti x y = x + y

„Idris“ galite pasakyti „funkcija paima du sveikuosius skaičius ir grąžina sveikąjį skaičių, tačiau pirmasis argumentas turi būti mažesnis už antrąjį“:


pridėti: (x: Nat) -> (y: Nat) -> (automatiškai mažesnis: LT x y) -> Nat pridėti x y = x + y

Jei bandysite iškviesti funkciją add 2 1 kur pirmasis argumentas yra didesnis nei antrasis, kompiliatorius atmes programą kompiliavimo metu. Neįmanoma parašyti programos, kurioje pirmasis argumentas yra didesnis nei antrasis. Retai kuri kalba turi tokią galimybę. Daugumoje kalbų šis patikrinimas įvyksta vykdymo metu: parašytume kažką panašaus į if x >= y: raise SomeError() .


Nėra Haskell atitikmens aukščiau pateiktame Idris pavyzdyje, ir nėra Go atitikmens nei Haskell, nei Idris pavyzdžiui. Dėl to Idris gali užkirsti kelią daugeliui klaidų, kurių Haskell negali išvengti, o Haskell gali užkirsti kelią daugeliui klaidų, kurių Go nepastebės. Abiem atvejais būtina papildomos funkcijos spausdinimo sistemas, kurios padarys kalbą sudėtingesnę.

Kai kurių statinių kalbų spausdinimo sistemos

Čia pateikiamas apytikslis kai kurių kalbų spausdinimo sistemų sąrašas didėjančia galia. Šis sąrašas suteiks jums bendrą supratimą apie sistemų galią, nelaikykite jo kaip absoliučios tiesos. Vienoje grupėje surinktos kalbos gali labai skirtis viena nuo kitos. Kiekviena spausdinimo sistema turi savo keistenybių, ir dauguma jų yra labai sudėtingos.

  • C (1972), Go (2009): Šios sistemos visai nėra galingos, be bendrųjų tipų palaikymo. Neįmanoma apibrėžti „MyList“ tipo, kuris reikštų „sveikųjų skaičių sąrašą“, „eilinių sąrašą“ ir kt. Vietoj to turėsite sudaryti „nepaskirtų vertybių sąrašą“. Programuotojas turi neautomatiniu būdu pranešti „tai yra eilučių sąrašas“ kiekvieną kartą, kai iš sąrašo gaunama eilutė, ir dėl to gali atsirasti vykdymo klaida.
  • Java (1995), C# (2000): Abi kalbos palaiko bendruosius tipus, todėl galite sakyti „MyList“. ir gauti sąrašą eilučių, apie kurias kompiliatorius žino ir gali vykdyti tipo taisykles. Sąrašo elementai bus String tipo, o kompiliatorius privers kompiliavimo taisykles, kaip įprasta, todėl vykdymo klaidų tikimybė yra mažesnė.
  • Haskell (1990), Rust (2010), Swift (2014): Visos šios kalbos turi keletą išplėstinių funkcijų, įskaitant bendruosius tipus, algebrinių duomenų tipus (ADT) ir tipų klases ar kažką panašaus (atitinkamai klasių tipus, bruožus ir protokolus). Rust ir Swift yra populiaresni nei Haskell ir juos reklamuoja didelės organizacijos (atitinkamai „Mozilla“ ir „Apple“).
  • Agda (2007), Idris (2011): Šios kalbos palaiko priklausomus tipus, todėl galite sukurti tokius tipus kaip „funkcija, kuri užima du sveikuosius skaičius x ir y, kur y yra didesnis už x“. Netgi „y yra didesnis už x“ apribojimas yra priverstinis kompiliavimo metu. Vykdant y niekada nebus mažesnis arba lygus x, nesvarbu, kas nutiktų. Šiose kalbose statiškai galima patikrinti labai subtilias, bet svarbias sistemos savybes. Labai mažai programuotojų jas studijuoja, tačiau šios kalbos kelia didelį jų entuziazmą.

Aiškus judėjimas link galingesnių spausdinimo sistemų, ypač vertinant kalbų populiarumą, o ne tik kalbų faktą. Reikšminga išimtis yra „Go“, kuri paaiškina, kodėl daugelis statinių kalbų šalininkų mano, kad tai žingsnis atgal.


Antroji grupė (Java ir C#) yra pagrindinės kalbos, brandžios ir plačiai naudojamos.


Trečioji grupė artėja prie pagrindinio srauto, kurią labai palaiko „Mozilla“ („Rust“) ir „Apple“ („Swift“).


Ketvirtoji grupė (Idris ir Agda) yra toli nuo pagrindinės krypties, tačiau laikui bėgant tai gali pasikeisti. Prieš dešimt metų trijų grupių kalbos buvo toli nuo pagrindinės.

Šiame straipsnyje pateikiamas būtinas minimumas iš tų dalykų, kuriuos tiesiog reikia žinoti apie spausdinimą, kad dinaminio spausdinimo nevadintumėte blogiu, Lisp – be tipo kalba, o C – stipriai spausdinama kalba.

Pilna versija yra Išsamus aprašymas visų tipų spausdinimas, pagardintas kodų pavyzdžiais, nuorodomis į populiarias programavimo kalbas ir iliustraciniais paveikslėliais.

Pirmiausia rekomenduoju perskaityti trumpąją straipsnio versiją, o tada, jei norite, pilną.

Trumpa versija

Remiantis spausdinimu, programavimo kalbos paprastai skirstomos į dvi dideles stovyklas - įvestas ir nespausdinamas (be tipo). Pirmasis apima, pavyzdžiui, C, Python, Scala, PHP ir Lua, o antrasis apima asamblėjos kalbą, Forth ir Brainfuck.

Kadangi „be tipo spausdinimas“ iš esmės yra paprastas kaip kištukas, jis nėra skirstomas į jokius kitus tipus. Tačiau įvestos kalbos yra suskirstytos į keletą daugiau sutampančių kategorijų:

  • Statinis/dinaminis spausdinimas. Statinis apibrėžiamas tuo, kad galutiniai kintamųjų ir funkcijų tipai nustatomi kompiliavimo metu. Tie. kompiliatorius jau 100% tikras, kuris tipas kur yra. Dinaminio spausdinimo metu visi tipai aptinkami programos vykdymo metu.

    Pavyzdžiai:
    Statinis: C, Java, C#;
    Dinaminis: Python, JavaScript, Ruby.

  • Stiprus / silpnas spausdinimas (taip pat kartais vadinamas stipriu / silpnu). Stiprus spausdinimas išsiskiria tuo, kad kalba neleidžia maišyti posakių Įvairių tipų ir neatlieka automatinių numanomų konversijų, pavyzdžiui, negalite atimti rinkinio iš eilutės. Silpnai įvestos kalbos daug numanomų konversijų atlieka automatiškai, net jei gali sumažėti tikslumas arba konversija yra dviprasmiška.

    Pavyzdžiai:
    Stiprios: Java, Python, Haskell, Lisp;
    Silpnas: C, JavaScript, Visual Basic, PHP.

  • Aiškus / numanomas spausdinimas. Aiškiai įvestos kalbos skiriasi tuo, kad turi būti aiškiai nurodytas naujų kintamųjų/funkcijų/jų argumentų tipas. Atitinkamai, kalbos su netiesioginiu spausdinimu perkelia šią užduotį kompiliatoriui / vertėjui.

    Pavyzdžiai:
    Aiškus: C++, D, C#
    Numanoma: PHP, Lua, JavaScript

Taip pat reikėtų pažymėti, kad visos šios kategorijos sutampa, pavyzdžiui, C kalba turi statinį silpną aiškų spausdinimą ir Python kalba- dinamiškas stiprus numanomas.

Tačiau nėra kalbų su statiniu ir dinaminiu spausdinimu vienu metu. Nors žvelgdamas į priekį pasakysiu, kad guliu čia - jie tikrai egzistuoja, bet apie tai vėliau.

Detali versija

Jei trumpos versijos jums nepakako, viskas gerai. Ar ne veltui parašiau išsamų? Svarbiausia, kad visos naudingos ir įdomios informacijos buvo tiesiog neįmanoma sutalpinti į trumpą variantą, o išsamus turbūt būtų per ilgas, kad visi perskaitytų neįsitempę.

Be tipo spausdinimas

Be tipo programavimo kalbose visi objektai laikomi tiesiog įvairaus ilgio bitų sekomis.

Be tipo spausdinimas dažniausiai būdingas žemo lygio (assembly language, Forth) ir ezoterinėms (Brainfuck, HQ9, Piet) kalboms. Tačiau, kartu su trūkumais, jis turi ir privalumų.

Privalumai
  • Leidžia rašyti itin žemu lygiu, o kompiliatorius/vertėjas netrukdys atlikti jokių tipo patikrų. Galite laisvai atlikti bet kokias operacijas su bet kokio tipo duomenimis.
  • Gautas kodas paprastai yra efektyvesnis.
  • Instrukcijų skaidrumas. Jei mokate kalbą, paprastai nekyla abejonių, kas yra tas ar kitas kodas.
Trūkumai
  • Sudėtingumas. Dažnai reikia pateikti sudėtingas reikšmes, tokias kaip sąrašai, eilutės ar struktūros. Tai gali sukelti nepatogumų.
  • Trūksta čekių. Bet kokie beprasmiai veiksmai, pavyzdžiui, žymeklio į masyvą atėmimas iš simbolio, bus laikomi visiškai normaliais, o tai kupina subtilių klaidų.
  • Žemas abstrakcijos lygis. Darbas su bet kokiu sudėtingu duomenų tipu nesiskiria nuo darbo su skaičiais, o tai, žinoma, sukels daug sunkumų.
Stiprus be tipo spausdinimas?

Taip, tai egzistuoja. Pavyzdžiui, asamblėjos kalba (x86/x86-64 architektūrai, kitų nežinau) negalite surinkti programos, jei bandote įkelti duomenis iš rax registro (64 bitai) į cx registrą (16 bitų). .

mov cx, eax ; surinkimo laiko klaida

Taigi paaiškėja, kad surinkėjas vis dar spausdina? Manau, kad šių patikrinimų neužtenka. Ir jūsų nuomonė, žinoma, priklauso tik nuo jūsų.

Statinis ir dinaminis spausdinimas

Pagrindinis dalykas, kuris skiria statinį spausdinimą nuo dinaminio spausdinimo, yra tai, kad visas tipo tikrinimas atliekamas kompiliavimo metu, o ne vykdymo metu.

Kai kurie žmonės gali manyti, kad statinis spausdinimas yra per daug ribojantis (iš tikrųjų taip ir yra, bet tai jau seniai buvo pašalinta naudojant kai kuriuos metodus). Kai kurie žmonės sako, kad dinamiškai įvestos kalbos žaidžia su ugnimi, bet kokios savybės išskiria jas? Ar tikrai abi rūšys turi galimybę egzistuoti? Jei ne, kodėl yra tiek daug kalbų, kurios įvedamos ir statiškai, ir dinamiškai?

Išsiaiškinkime.

Statinio spausdinimo pranašumai
  • Tipo tikrinimas atliekamas tik vieną kartą – kompiliavimo etape. Tai reiškia, kad mums nereikės nuolat aiškintis, ar bandome padalyti skaičių iš eilutės (ir padarome klaidą, ar atliekame konversiją).
  • Vykdymo greitis. Iš ankstesnio punkto aišku, kad statiškai įvestos kalbos beveik visada yra greitesnės nei dinamiškai įvestos.
  • Tam tikromis papildomomis sąlygomis jis leidžia aptikti galimas klaidas jau kompiliavimo etape.
Dinaminio rašymo pranašumai
  • Universalių kolekcijų kūrimo paprastumas - krūvos visko ir visų (retai toks poreikis iškyla, bet kai atsiranda dinaminis spausdinimas, tai padės).
  • Patogumas aprašyti apibendrintus algoritmus (pavyzdžiui, masyvo rūšiavimas, kuris veiks ne tik sveikųjų skaičių sąraše, bet ir realiųjų skaičių sąraše ir net eilučių sąraše).
  • Lengva išmokti – dinamiškai įvestos kalbos paprastai yra labai tinkamos norint pradėti programuoti.

Apibendrintas programavimas

Gerai, svarbiausias dinaminio spausdinimo argumentas yra bendrųjų algoritmų aprašymo patogumas. Įsivaizduokime problemą – mums reikia funkcijos, kad galėtume ieškoti keliuose masyvuose (arba sąrašuose) – sveikųjų skaičių masyvą, realiųjų skaičių masyvą ir simbolių masyvą.

Kaip mes tai išspręsime? Išspręskime tai 3 skirtingomis kalbomis: viena su dinaminiu spausdinimu ir dviem su statiniu.

Naudosiu vieną iš paprasčiausių paieškos algoritmų – brute force. Funkcija gaus ieškomą elementą, patį masyvą (arba sąrašą) ir grąžins elemento indeksą, o jei elementas nerastas – (-1).

Dinaminis sprendimas („Python“):

Def find(required_element, list): for (indeksas, elementas) in enumerate (sąrašas): if elementas == reikalingas_elementas: grąžina indeksą grąžinti (-1)

Kaip matote, viskas paprasta ir nėra problemų dėl to, kad sąraše gali būti skaičių, sąrašų ar kitų masyvų. Labai gerai. Eikime toliau – tą pačią problemą išspręskite C!

Statinis tirpalas (C):

Unsigned int find_int(int reikalingas_elementas, int masyvas, unsigned int dydis) ( for (unsigned int i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

Na, kiekviena funkcija atskirai yra panaši į Python versiją, bet kodėl jos yra trys? Ar statinis programavimas tikrai pasimetė?

Taip ir ne. Yra keletas programavimo būdų, iš kurių vieną dabar apsvarstysime. Tai vadinama bendruoju programavimu, o C++ kalba tai gana gerai palaiko. Pažvelkime į naują versiją:

Statinis sprendimas (bendrasis programavimas, C++):

Šablonas unsigned int find(T reikalingas_elementas, std::vektorius masyvas) ( for (nesigned int i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

gerai! Tai neatrodo daug sudėtingesnė nei Python versija ir nereikalauja daug rašymo. Be to, mes turime visų masyvų įgyvendinimą, o ne tik 3, reikalingus problemai išspręsti!

Atrodo, kad ši versija yra būtent tai, ko mums reikia – gauname ir statinio spausdinimo, ir kai kuriuos dinaminio spausdinimo pranašumus.

Puiku, kad tai apskritai įmanoma, bet gali būti dar geriau. Pirma, apibendrintas programavimas gali būti patogesnis ir gražesnis (pavyzdžiui, Haskell kalba). Antra, be apibendrinto programavimo galima naudoti ir polimorfizmą (rezultatas bus prastesnis), funkcijų perkrovimą (panašiai) ar makrokomandas.

Statika dinamikoje

Taip pat reikėtų paminėti, kad daugelis statinių kalbų leidžia dinamiškai įvesti tekstą, pavyzdžiui:

  • C# palaiko dinaminį pseudo tipą.
  • F# palaiko sintaksinį cukrų operatoriaus ? forma, kurio pagrindu galima įgyvendinti dinaminio tipavimo imitaciją.
  • Haskell – dinaminį spausdinimą užtikrina modulis Data.Dynamic.
  • Delphi – per specialų Variant tipą.

Be to, kai kurios dinamiškai įvestos kalbos leidžia pasinaudoti statinio spausdinimo pranašumais:

  • Common Lisp – tipo deklaracijos.
  • Perl – nuo ​​5.6 versijos, gana ribota.

Stiprus ir silpnas spausdinimas

Stipriai įvestos kalbos neleidžia maišyti skirtingų tipų objektų išraiškose ir jų neatlieka automatinės konversijos. Jie taip pat vadinami „stipriai spausdintomis kalbomis“. Anglų kalbos terminas yra stiprus rašymas.

Silpnai įvestos kalbos, atvirkščiai, skatina programuotoją maišyti skirtingus tipus vienoje išraiškoje, o pats kompiliatorius viską sumažins iki vieno tipo. Jos dar vadinamos „laisvai spausdintomis kalbomis“. Anglų kalbos terminas yra silpnas rašymas.

Silpnas spausdinimas dažnai painiojamas su dinaminiu spausdinimu, kuris yra visiškai neteisingas. Dinamiškai spausdinama kalba gali būti tiek silpnai, tiek stipriai.

Tačiau nedaugelis žmonių skiria spausdinimo griežtumą. Dažnai teigiama, kad jei kalba yra statiškai spausdinama, galite sugauti daugybę galimų klaidų sudarant. Jie tau meluoja!

Kalba taip pat turi būti stipri. Iš tiesų, jei kompiliatorius, užuot pranešęs apie klaidą, tiesiog prideda prie skaičiaus eilutę arba, dar blogiau, iš vieno masyvo atima kitą, kokia mums nauda, ​​kad visos tipų „patikros“ bus kompiliavimo metu. etapas? Teisingai – silpnas statinis spausdinimas yra dar blogesnis nei stiprus dinaminis spausdinimas! (na tokia mano nuomone)

Taigi ar silpnas spausdinimas neturi jokių pranašumų? Tai gali atrodyti taip, bet nepaisant to, kad esu karštas stipraus spausdinimo šalininkas, turiu sutikti, kad silpnas spausdinimas turi ir privalumų.

Norite sužinoti, kurios?

Stipraus spausdinimo pranašumai
  • Patikimumas – vietoj neteisingo elgesio gausite išimtį arba kompiliavimo klaidą.
  • Greitis - Vietoj paslėptų konversijų, kurios gali būti gana brangios, su stipriu spausdinimu turite jas parašyti aiškiai, o tai verčia programuotoją bent jau žinoti, kad ši kodo dalis gali būti lėta.
  • Programos veikimo supratimas – vėlgi, vietoj numanomo tipo liejimo, programuotojas viską rašo pats, vadinasi, maždaug supranta, kad eilutės ir skaičiaus palyginimas nevyksta savaime ir ne burtų keliu.
  • Tikrumas – kai rašote transformacijas ranka, tiksliai žinote, ką konvertuojate ir į ką. Taip pat visada žinosite, kad dėl tokių konversijų gali sumažėti tikslumas ir gauti neteisingi rezultatai.
Silpno rašymo pranašumai
  • Patogus naudoti mišrius reiškinius (pavyzdžiui, iš sveikųjų ir realiųjų skaičių).
  • Abstrahuotis nuo spausdinimo ir sutelkti dėmesį į užduotį.
  • Įrašo trumpumas.

Gerai, išsiaiškinome, pasirodo, silpnas spausdinimas turi ir privalumų! Ar yra būdų, kaip silpno spausdinimo pranašumus perkelti į stiprų spausdinimą?

Pasirodo, yra net du.

Netiesioginis tipo liejimas, nedviprasmiškose situacijose ir neprarandant duomenų

Oho... Gana ilga mintis. Leiskite dar labiau sutrumpinti iki „riboto numanomo konvertavimo“. Ką reiškia nedviprasmiška situacija ir duomenų praradimas?

Vienareikšmiška situacija – tai transformacija arba operacija, kurios esmė iš karto aiškėja. Pavyzdžiui, dviejų skaičių pridėjimas yra vienareikšmė situacija. Bet skaičių konvertuoti į masyvą nėra (galbūt bus sukurtas vieno elemento masyvas, galbūt tokio ilgio masyvas, pagal numatytuosius nustatymus užpildytas elementais ir galbūt skaičius bus konvertuotas į eilutę, o tada į masyvą simbolių).

Prarasti duomenis dar lengviau. Jei realųjį skaičių 3,5 konvertuosime į sveikąjį skaičių, prarasime dalį duomenų (tiesą sakant, ši operacija irgi dviprasmiška – kaip bus atliktas apvalinimas? Aukštyn? Žemyn? Trupmeninę dalį atmesti?).

Konversijos dviprasmiškose situacijose ir konversijos su duomenų praradimu yra labai, labai blogos. Programavime nėra nieko blogesnio už tai.

Jei netikite manimi, studijuokite PL/I kalbą arba net tiesiog pasižiūrėkite jos specifikaciją. Jame yra VISŲ duomenų tipų konvertavimo taisyklės! Tai tik pragaras!

Gerai, prisiminkime apie ribotą numanomą konversiją. Ar yra tokių kalbų? Taip, pavyzdžiui, Pascal galite konvertuoti sveikąjį skaičių į tikrąjį skaičių, bet ne atvirkščiai. Panašių mechanizmų taip pat yra C#, Groovy ir Common Lisp.

Gerai, sakiau, kad vis dar yra būdas gauti keletą silpno spausdinimo stipria kalba pranašumų. Ir taip, jis egzistuoja ir vadinamas konstruktoriaus polimorfizmu.

Paaiškinsiu tai naudodamas nuostabios Haskell kalbos pavyzdį.

Polimorfiniai konstruktoriai atsirado pastebėjus, kad saugios numanomos konversijos dažniausiai reikalingos, kai naudojami skaitiniai literalai.

Pavyzdžiui, reiškinyje pi + 1 nenorite rašyti pi + 1.0 arba pi + float(1) . Aš tiesiog noriu parašyti pi + 1!

Ir tai daroma Haskell dėl to, kad pažodinis 1 neturi konkretus tipas. Ji nėra nei vientisa, nei tikra, nei sudėtinga. Tai tik skaičius!

Dėl to, rašydami paprastą funkciją suma x y , padauginus visus skaičius nuo x iki y (su prieaugiu 1), gauname iš karto kelias versijas - sveikųjų skaičių suma, realiųjų suma, racionaliųjų suma, kompleksinių skaičių suma ir net visų tų skaičių tipų, kuriuos jūs pats apibrėžėte, suma.

Žinoma, ši technika taupo tik naudojant mišrius posakius su skaitiniais literalais, ir tai tik ledkalnio viršūnė.

Taigi galime teigti, kad geriausias sprendimas yra balansuoti ant ribos tarp stipraus ir silpno spausdinimo. Tačiau jokia kalba dar nesukuria tobulos pusiausvyros, todėl labiau linkstu į stipriai įvestas kalbas (pvz., Haskell, Java, C#, Python), o ne silpnai įvestas kalbas (pvz., C, JavaScript, Lua, PHP).

Aiškus ir numanomas rašymas

Aiškiai įvesta kalba reikalauja, kad programuotojas nurodytų visų deklaruojamų kintamųjų ir funkcijų tipus. Angliškas terminas yra aiškus spausdinimas.

Kita vertus, netiesiogiai įvesta kalba skatina pamiršti tipus ir palikti tipus daryti kompiliatoriui arba vertėjui. Anglų kalbos terminas yra implicit typing.

Iš pradžių galite manyti, kad numanomas spausdinimas yra tolygus dinaminiam, o aiškus – statiniam, bet vėliau pamatysime, kad taip nėra.

Ar kiekvienas tipas turi pranašumų ir vėlgi, ar yra jų derinių ir ar yra kalbų, kurios palaiko abu metodus?

Aiškaus rašymo pranašumai
  • Kiekviena funkcija turi parašą (pavyzdžiui, int add(int, int)), todėl lengva nustatyti, ką funkcija atlieka.
  • Programuotojas iš karto užrašo, kokio tipo reikšmes galima išsaugoti konkrečiame kintamajame, todėl nereikia jo atsiminti.
Netiesioginio spausdinimo pranašumai
  • Trumpasis žymėjimas - def add(x, y) yra aiškiai trumpesnis nei int add(int x, int y) .
  • Atsparumas pokyčiams. Pavyzdžiui, jei funkcijoje laikinas kintamasis buvo to paties tipo kaip įvesties argumentas, tai aiškiai įvestoje kalboje, keičiant įvesties argumento tipą, reikės pakeisti ir laikinojo kintamojo tipą.

Gerai, aišku, kad abu metodai turi ir pliusų, ir minusų (kas ko nors kito tikėjosi?), tad ieškokime būdų, kaip sujungti šiuos du būdus!

Aiškus spausdinimas pagal pasirinkimą

Yra kalbų su numanomu spausdinimu pagal numatytuosius nustatymus ir galimybe nurodyti reikšmių tipą, jei reikia. Vertėjas automatiškai išves tikrąjį išraiškos tipą. Viena iš šių kalbų yra Haskell, aiškumo dėlei leiskite pateikti paprastą pavyzdį:

Be aiškios tipo specifikacijos pridėti (x, y) = x + y – aiški tipo specifikacija pridėti:: (Sveikasis skaičius, Sveikasis skaičius) -> Sveikasis skaičius pridėti (x, y) = x + y

Pastaba: aš tyčia naudojau nepakartojamą funkciją ir taip pat sąmoningai parašiau asmeninį parašą vietoj bendresnio priedo:: (Num a) -> a -> a -> a , nes Norėjau parodyti idėją nepaaiškindamas Haskell sintaksės.

Hm. Kaip matome, jis labai gražus ir trumpas. Funkcijai įrašyti reikia tik 18 simbolių vienoje eilutėje, įskaitant tarpus!

Tačiau automatinio tipo išvados yra gana sudėtingas dalykas ir netgi tokia šauni kalba kaip Haskell kartais nepavyksta. (pavyzdys yra monomorfizmo apribojimas)

Ar yra kalbų, kuriose pagal numatytuosius nustatymus naudojamas aiškus spausdinimas ir, jei reikia, numanomas? Con
tikrai.

Netiesioginis spausdinimas pagal pasirinkimą

Naujasis C++ kalbos standartas, vadinamas C++11 (anksčiau vadintas C++0x), įvedė automatinį raktinį žodį, leidžiantį kompiliatoriui pagal kontekstą nustatyti tipą:

Palyginkime: // Rankiniu būdu nurodant tipą unsigned int a = 5; nepasirašytas int b = a + 3; // Unsigned tipo automatinė išvestis in a = 5; auto b = a + 3;

Neblogai. Tačiau įrašų nedaug sumažėjo. Pažiūrėkime į pavyzdį su iteratoriais (jei nesuprantate, nebijokite, svarbiausia atkreipti dėmesį į tai, kad dėl automatinio išvesties įrašymas labai sumažėja):

// Rankiniu būdu nurodant std::vektoriaus tipą vec = atsitiktinisVektorius(30); for (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // Automatinė tipo išvada auto vec = randomVector (trisdešimt); for (auto it = vec.cbegin(); ...) ( ... )

Oho! Tai yra santrumpa. Gerai, bet ar galima padaryti kažką panašaus į Haskell, kur grąžinimo tipas priklauso nuo argumentų tipų?

Ir vėl atsakymas yra taip, dėka raktinio žodžio decltype kartu su auto:

// Rankinis tipas int divide(int x, int y) (... ) // Automatinė tipo išvada auto divide(int x, int y) -> decltype(x / y) (... )

Ši žymėjimo forma gali neatrodyti labai gerai, tačiau kartu su bendru programavimu (šablonai / bendrieji dalykai), numanomas spausdinimas arba automatinė tipo išvada daro stebuklus.

Kai kurios programavimo kalbos pagal šią klasifikaciją

Pateiksiu nedidelį populiarių kalbų sąrašą ir parašysiu, kaip jos skirstomos į kiekvieną „spausdinimo“ kategoriją.

JavaScript – Dinaminis / Silpnas / Netiesioginis rubinas - Dinaminis / Stiprus / Netiesioginis Python - Dinaminis / Stiprus / Netiesioginis Java - Statinis / Stiprus / Aiškus PHP - Dinaminis / Silpnas / Netiesioginis C - Statinis / Silpnas / Aiškus C++ - Statinis / Pusiau stiprus / Aiškus „Perl“ – dinaminis / silpnas / netiesioginis tikslas-C – statinis / silpnas / aiškus C# – statiškas / stiprus / aiškus „Haskell“ – statiškas / stiprus / numanomas bendras Lisp – dinaminis / stiprus / numanomas

Galbūt kažkur suklydau, ypač su CL, PHP ir Obj-C, jei turite kitokią nuomonę apie kurią nors kalbą, rašykite komentaruose.

Tipavimas – tipo priskyrimas informacijos subjektams.

Dažniausiai naudojami primityvūs duomenų tipai:

  • Skaitmeninis
  • Simboliška
  • Logiška

Pagrindinės duomenų tipų sistemos funkcijos:

  • Saugumas
    Kiekviena operacija tikrinama siekiant užtikrinti, kad ji gautų būtent tokio tipo argumentus, kuriems ji skirta;
  • Optimizavimas
    Pagal tipą parenkamas efektyvaus saugojimo būdas ir jo apdorojimo algoritmai;
  • Dokumentacija
    Pabrėžiami programuotojo ketinimai;
  • Abstrakcija
    Aukšto lygio duomenų tipų naudojimas leidžia programuotojui galvoti apie reikšmes kaip apie aukšto lygio objektus, o ne kaip apie bitų rinkinį.

klasifikacija

Yra daug programavimo kalbų tipų klasifikacijų, tačiau yra tik 3 pagrindinės:

Statinis/dinaminis spausdinimas

Statinis - tipo nuoseklumo priskyrimas ir patikrinimas atliekamas kompiliavimo etape. Duomenų tipai yra susieti su kintamaisiais, o ne su konkrečiomis reikšmėmis. Statinis spausdinimas leidžia kompiliavimo etape rasti spausdinimo klaidas, padarytas retai naudojamose programos logikos šakose.

Dinaminis spausdinimas yra priešingas statiniam spausdinimui. Dinaminio spausdinimo metu visi tipai aptinkami programos vykdymo metu.

Dinaminis spausdinimas leidžia sukurti lankstesnę programinę įrangą, nors tai yra didesnė spausdinimo klaidų tikimybė. Vienetų testavimas tampa ypač svarbus kuriant programinė įranga programavimo kalbose su dinaminiu spausdinimu, nes tai yra vienintelis būdas rasti spausdinimo klaidas, padarytas retai naudojamose programų logikos šakose.

Dinaminis spausdinimas

Var laimingas skaičius = 777; var siteName = "Tyapk"; // prisiimame skaičių, įrašome eilutę var wrongNumber = "999";

Statinis spausdinimas

Tegul laimingas skaičius: skaičius = 777; let siteName: string = "Tyapk"; // sukels klaidą tegul wrongNumber: number = "999";

  • Statinė: Java, C#, TypeScript.
  • Dinaminis: Python, Ruby, JavaScript.

Aiškus / numanomas spausdinimas.

Aiškiai įvestos kalbos skiriasi tuo, kad turi būti aiškiai nurodytas naujų kintamųjų/funkcijų/jų argumentų tipas. Atitinkamai, kalbos su netiesioginiu spausdinimu perkelia šią užduotį kompiliatoriui / vertėjui. Aiškus spausdinimas yra priešingas numanomam spausdinimui.

Norint aiškiai įvesti tekstą, kiekvienam naudojamam kintamajam reikalinga aiškaus tipo deklaracija. Šis spausdinimo tipas yra ypatingas statinio spausdinimo atvejis, nes kiekvieno kintamojo tipas nustatomas kompiliavimo metu.

Netiesioginis spausdinimas

Tegul stringVar = "777" + 99; // gauti "77799"

Aiškus spausdinimas (išgalvota kalba, panaši į JS)

Tegul klaidingasStringVar = "777" + 99; // sukels klaidą tegul stringVar = "777" + String(99); // gauti "77799"

Stiprus / nestiprus spausdinimas

Taip pat vadinamas stipriu / silpnu spausdinimu. Naudojant griežtą spausdinimą, tipai priskiriami „vieną kartą ir visiems laikams“, o naudojant negriežtą spausdinimą, jie gali keistis vykdant programą.

Stipriai įvestos kalbos draudžia keisti kintamojo duomenų tipą ir leidžia konvertuoti tik aiškų duomenų tipą. Stiprus spausdinimas išsiskiria tuo, kad kalba neleidžia maišyti skirtingų tipų išraiškose ir neatlieka automatinių numanomų konversijų, pavyzdžiui, negalite atimti skaičiaus iš eilutės. Silpnai įvestos kalbos daug numanomų konversijų atlieka automatiškai, net jei gali sumažėti tikslumas arba konversija yra dviprasmiška.

Stiprus spausdinimas (išgalvota kalba, panaši į JS)

Tegul klaidingas skaičius = 777; klaidingasNumber = klaidingasNumber + "99"; // gauname klaidą, kad eilutė pridedama prie skaitmeninio kintamojo wrongNumber tegul trueNumber = 777 + Number("99"); // gauti 876

Negriežtas spausdinimas (kaip yra js)

Tegul klaidingas skaičius = 777; klaidingasNumber = klaidingasNumber + "99"; // gavo eilutę "77799"

  • Griežtas: Java, Python, Haskell, Lisp.
  • Negriežta: C, JavaScript, Visual Basic, PHP.
Dalintis