Çdo gjuhë - Pse nevojitet shtypja dinamike? Program arsimor për shtypjen në gjuhët e programimit Shtypja dinamike.

Gjithçka është shumë e thjeshtë. Është si ndryshimi midis një hoteli dhe një apartamenti privat.

Në apartament banojnë vetëm ata që janë të regjistruar aty. Nëse, të themi, familja Sidorov jeton në të, atëherë familja Pupkin, për jetën tonë, nuk mund të jetojë atje. Në të njëjtën kohë, Petya Sidorov mund të jetojë në këtë apartament, atëherë Grisha Sidorov mund të zhvendoset atje (ndonjëherë ata madje mund të jetojnë atje në të njëjtën kohë - kjo është një grup). Ky është shtypje statike.

Familja Sidorov mund të jetojë në hotel njëherësh. Ata as nuk duhet të regjistrohen gjithmonë atje. Pastaj ata do të largohen nga atje, dhe Pupkins do të lëvizin atje. Dhe pastaj Kuznetsovët. Dhe pastaj dikush tjetër. Ky është shtypje dinamike.

Nëse i kthehemi programimit, atëherë rasti i parë (shtypja statike) gjendet, të themi, në gjuhët C, C++, C#, Java dhe të tjera. Përpara se t'i caktoni një vlerë një ndryshoreje për herë të parë, duhet të tregoni se çfarë do të ruani atje: numra të plotë, numra me pikë lundruese, vargje, etj. Sidorovët do të jetojnë në këtë apartament). Nga ana tjetër, shtypja dinamike nuk e kërkon këtë. Kur caktoni një vlerë, ju caktoni njëkohësisht variablin llojin e saj ( Vasya Pupkin nga familja Pupkin tani jeton në këtë dhomë hoteli). Kjo gjendet në gjuhë të tilla si PHP, Python dhe JavaScript.

Të dyja qasjet kanë avantazhet dhe disavantazhet e tyre. Cili është më i mirë apo më i keq varet nga problemet që zgjidhen. Mund të lexoni më shumë në detaje, të themi, në Wikipedia.

Me shtypjen statike, ju e dini saktësisht llojin e ndryshores në momentin e shkrimit të programit dhe zhvillimit të algoritmit dhe e merrni këtë parasysh. ato. nëse thoni që ndryshorja G është një numër i plotë pa shenjë katër bajtësh, atëherë në algoritëm do të jetë gjithmonë një numër i plotë i pashenjë me katër bajtë (nëse ka ndonjë gjë, atëherë duhet ta konvertoni në mënyrë eksplicite ose të dini se si përkthyesi e konverton atë në një të caktuar varg situatash, por kryesisht nëse ka një mospërputhje tipi është një gabim në algoritëm, dhe përpiluesi të paktën do t'ju paralajmërojë), me një statik, nuk mund të vendosni vargun "Vasya budallai" në numër. dhe kontrolle shtesë përpara se të përdorni variablin për të përcaktuar "a ka një numër atje" nuk kërkohen, ju kryeni të gjithë korrektësinë e të dhënave në momentin e hyrjes së tyre në program ose siç kërkohet nga vetë algoritmi.

me shtypjen dinamike, lloji i së njëjtës variabël është përgjithësisht i panjohur për ju dhe mund të ndryshojë tashmë gjatë ekzekutimit të programit, dhe ju e merrni parasysh këtë, askush nuk do t'ju paralajmërojë për një gabim të mundshëm në algoritëm për shkak të mospërputhjes së tipit (kur zhvilloni algoritmin supozuat se G është një numër i plotë dhe përdoruesi futi, të themi, një numër me pikë lundruese, ose më keq, një varg, ose të themi, pas një operacioni aritmetik, në vend të një numri të plotë, kishte një numër me pikë lundruese , dhe në hapin tjetër do të përpiqeni të përdorni operacione bit...), nga ana tjetër nuk keni pse të shqetësoheni për shumë gjëra të vogla.

Thjeshtësia e shtypjes në qasjen OO është pasojë e thjeshtësisë së modelit të llogaritjes së objektit. Duke lënë mënjanë detajet, mund të themi se gjatë ekzekutimit të një sistemi të orientuar nga objekti, ndodh vetëm një lloj ngjarjeje - një thirrje tipare:


do të thotë të kryesh një operacion f mbi një objekt të bashkangjitur x, me kalimin e argumentit arg(ndoshta disa argumente ose asnjë fare). Programuesit e Smalltalk flasin në këtë rast për "kalimin e një objekti x mesazhe f me argument arg", por ky është vetëm një ndryshim në terminologji, dhe për këtë arsye është i parëndësishëm.

Që gjithçka bazohet në këtë Konstruksion Bazë shpjegon një pjesë të ndjenjës së bukurisë së ideve të OO.

Nga Dizajni Bazë ndiqni ato situata jonormale që mund të lindin gjatë ekzekutimit:

Përkufizimi: shkelje e tipit

Një shkelje e tipit të kohës së ekzekutimit, ose thjesht një shkelje e tipit shkurt, ndodh në momentin e telefonatës x.f(arg), Ku x ngjitur me objektin O.B.J., nëse njëra prej tyre:

[x]. nuk ka asnjë komponent përkatës f dhe të zbatueshme për O.B.J.,

[x]. një komponent i tillë ekziston, megjithatë, argumenti arg e papranueshme për të.

Problemi me shkrimin është të shmangni situata si kjo:

Problemi i shtypjes së sistemeve OO

Kur zbulojmë se mund të ndodhë një shkelje e tipit gjatë ekzekutimit të një sistemi të orientuar nga objekti?

Fjala kyçe është Kur. Herët a vonë do të kuptoni se ka një shkelje të llojit. Për shembull, përpjekja për të ekzekutuar komponentin Launch Torpedo në objektin Employee nuk do të funksionojë dhe do të dështojë. Sidoqoftë, mund të preferoni të gjeni gabime më shpejt sesa më vonë.

Shtypje statike dhe dinamike

Megjithëse opsionet e ndërmjetme janë të mundshme, këtu janë dy qasje kryesore:

[x]. Shtypja dinamike: prisni derisa çdo telefonatë të përfundojë dhe më pas merrni një vendim.

[x]. Shtypja statike: Duke pasur parasysh një sërë rregullash, përcaktoni nga teksti burimor nëse shkeljet e tipit janë të mundshme gjatë ekzekutimit. Sistemi ekzekutohet nëse rregullat garantojnë që nuk do të ketë gabime.

Këto terma janë të lehta për t'u shpjeguar: me shtypjen dinamike, kontrollimi i tipit ndodh gjatë kohës që sistemi është në punë (në mënyrë dinamike), ndërsa me shtypjen statike, kontrollimi i tipit kryhet në tekst në mënyrë statike (para ekzekutimit).

Shtypja statike përfshin kontrollin automatik, i cili zakonisht bëhet nga përpiluesi. Si rezultat, ne kemi një përkufizim të thjeshtë:

Përkufizimi: gjuhë e shtypur në mënyrë statike

Një gjuhë e orientuar nga objekti shtypet në mënyrë statike nëse vjen me një sërë rregullash të qëndrueshme, të kontrolluara nga përpiluesi që sigurojnë që ekzekutimi i sistemit të mos çojë në shkelje të tipit.

Termi " të fortë duke shtypur" ( të fortë). Ai korrespondon me natyrën ultimatum të përkufizimit, që kërkon mungesë të plotë të shkeljes së llojit. Është gjithashtu e mundur i dobët (i dobët) forma të shtypjes statike, në të cilat rregullat eliminojnë shkelje të caktuara pa i eliminuar ato plotësisht. Në këtë kuptim, disa gjuhë të orientuara nga objekti janë statikisht të shtypura dobët. Do të luftojmë për tipizimin më të fortë.

Gjuhët e shtypura në mënyrë dinamike, të njohura si gjuhë të pashtypura, nuk kanë deklarata të tipit dhe entitetet mund të kenë ndonjë vlerë të bashkangjitur me to në kohën e ekzekutimit. Kontrollimi i tipit statik nuk është i mundur në to.

Rregullat e shtypjes

Shënimi ynë OO është i shtypur në mënyrë statike. Rregullat e tipit të tij u prezantuan në leksionet e mëparshme dhe përfundojnë në tre kërkesa të thjeshta.

[x]. Gjatë deklarimit të çdo entiteti ose funksioni, lloji i tij duhet të specifikohet, për shembull, acc: LLOGARIA. Çdo rutinë ka 0 ose më shumë argumente formale, lloji i të cilave duhet të specifikohet, për shembull: vendos (x:G;i:INTEGER).

[x]. Në çdo detyrë x:=y dhe për çdo thirrje në një nënprogram në të cilën yështë argumenti aktual për argumentin formal x, lloji i burimit y duhet të jetë në përputhje me llojin e synuar x. Përkufizimi i përputhshmërisë bazohet në trashëgiminë: B i përputhshëm me A, nëse është pasardhës i tij, i plotësuar me rregulla për parametrat gjenerikë (shih Leksionin 14).

[x]. Thirrni x.f(arg) e kërkon atë f ishte një komponent i klasës bazë për llojin e synuar x, Dhe f duhet të eksportohet në klasën në të cilën shfaqet thirrja (shih 14.3).

Realizmi

Megjithëse përkufizimi i një gjuhe të shtypur në mënyrë statike është dhënë mjaft saktë, nuk mjafton - nevojiten kritere joformale kur krijohen rregullat e shtypjes. Le të shqyrtojmë dy raste ekstreme.

[x]. Gjuhë absolutisht e saktë, në të cilin çdo sistem sintaksorisht i saktë është gjithashtu tip i saktë. Rregullat e deklarimit të tipit nuk janë të nevojshme. Gjuhë të tilla ekzistojnë (imagjinoni një shënim polak për shtimin dhe zbritjen e numrave të plotë). Fatkeqësisht, asnjë gjuhë e vërtetë universale nuk e plotëson këtë kriter.

[x]. Gjuhë krejtësisht e pasaktë, e cila është e lehtë për t'u krijuar duke marrë çdo gjuhë ekzistuese dhe duke shtuar një rregull shtypjeje që bën ndonjë sistemi është i pasaktë. Sipas përkufizimit, gjuha është e shtypur: meqenëse nuk ka sisteme që ndjekin rregullat, asnjë sistem nuk do të shkaktojë shkelje të tipit.

Mund të themi se gjuhët e tipit të parë të përshtatshme, Por të padobishme, kjo e fundit mund të jetë e dobishme, por jo e përshtatshme.

Në praktikë, ne kemi nevojë për një sistem tipi që është njëkohësisht i përdorshëm dhe i dobishëm: mjaftueshëm i fuqishëm për të përmbushur nevojat e llogaritjes dhe mjaftueshëm i përshtatshëm për të mos na detyruar në komplikime për të kënaqur rregullat e shtypjes.

Le të themi atë gjuhë realiste, nëse është i përdorshëm dhe i dobishëm në praktikë. Në ndryshim nga përkufizimi i shtypjes statike, i cili i jep një përgjigje kategorike pyetjes: " A është gjuha X e shtypur në mënyrë statike?“, përkufizimi i realizmit është pjesërisht subjektiv.

Në këtë leksion do të sigurohemi që shënimi që propozojmë të jetë realist.

Pesimizmi

Shkrimi statik çon nga natyra e tij në një politikë "pesimiste". Një përpjekje për të siguruar atë të gjitha llogaritjet nuk çojnë në dështime, refuzon llogaritjet që mund të përfundonin pa gabime.

Konsideroni një gjuhë të rregullt, jo objektive, të ngjashme me Paskalin me lloje të ndryshme REAL Dhe I PLOTË. Kur përshkruani n:NJERI I PLOTE; r: Real operatori n:=r do të refuzohet si shkelje e rregullave. Kështu, përpiluesi do të refuzojë të gjitha deklaratat e mëposhtme:


Nëse lejojmë ekzekutimin e tyre, do të shohim se [A] do të funksionojë gjithmonë, pasi çdo sistem numrash ka një paraqitje të saktë të numrit real 0,0, i cili përkthehet pa mëdyshje në 0 numra të plotë. [B] pothuajse me siguri do të funksionojë gjithashtu. Rezultati i veprimit [C] nuk është i dukshëm (duam ta marrim rezultatin duke rrumbullakosur apo hedhur poshtë pjesën thyesore?). [D] do të bëjë punën, ashtu si operatori:


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

e cila përfshin detyrën e paarritshme ( n^2është katrori i një numri n). Pas zëvendësimit n^2n Vetëm një seri vrapimesh do të japë rezultatin e duhur. Detyrë n një vlerë e madhe reale që nuk mund të përfaqësohet në tërësi do të çojë në dështim.

Në gjuhët e shtypura, të gjithë këta shembuj (punojnë, nuk funksionojnë, ndonjëherë punojnë) trajtohen pa mëshirë si shkelje të rregullave të përshkrimit të tipit dhe refuzohen nga çdo përpilues.

Pyetja nuk është ne do nëse jemi pesimistë, por fakti është, sa shumë ne mund të lejojmë të jemi pesimistë. Kthimi te kërkesa e realizmit: nëse rregullat e tipit janë aq pesimiste sa ndërhyjnë në lehtësinë e shkrimit të llogaritjeve, ne do t'i refuzojmë ato. Por nëse arritja e sigurisë së tipit vjen me koston e vogël të fuqisë shprehëse, ne do t'i pranojmë ato. Për shembull, në një mjedis zhvillimi që ofron funksione rrumbullakimi dhe nënvizimi - rrumbullakët Dhe cungoj, operator n:=r konsiderohet e pasaktë, me të drejtë, sepse ju detyron të shkruani në mënyrë eksplicite konvertimin real në numër të plotë, në vend që të përdorni konvertimet e paqarta të paracaktuara.

Shtypja statike: si dhe pse

Megjithëse përfitimet e shtypjes statike janë të dukshme, është mirë të flasim përsëri për to.

Përparësitë

Ne renditëm arsyet e përdorimit të shtypjes statike në teknologjinë e objekteve në fillim të leksionit. Këto janë besueshmëria, lehtësia e të kuptuarit dhe efikasiteti.

Besueshmëria shkaktohet nga zbulimi i gabimeve që përndryshe mund të shfaqeshin vetëm gjatë funksionimit, dhe vetëm në disa raste. E para nga rregullat, e cila detyron entitetet të deklarohen, si dhe funksionet, prezanton tepricë në tekstin e programit, gjë që i lejon kompajlerit, duke përdorur dy rregullat e tjera, të zbulojë mospërputhjet midis përdorimit të synuar dhe atij aktual të entiteteve, komponentëve dhe shprehjet.

Zbulimi i hershëm i gabimeve është gjithashtu i rëndësishëm sepse sa më gjatë të presim për t'i gjetur ato, aq më shumë do të rriten kostot e korrigjimit. Kjo veti, e kuptueshme intuitivisht për të gjithë programuesit profesionistë, konfirmohet në mënyrë sasiore nga veprat e njohura të Boehm. Varësia e kostove të korrigjimit nga koha për të gjetur gabimet tregohet në një grafik të bazuar në të dhënat nga një numër projektesh të mëdha industriale dhe eksperimente të kryera me një projekt të vogël të kontrolluar:

Oriz. 17.1. Kostot krahasuese të korrigjimit të gabimeve (publikuar me leje)

Lexueshmëria ose Lehtë për t'u kuptuar(lexueshmëria) ka avantazhet e veta. Në të gjithë shembujt në këtë libër, shfaqja e një tipi në një entitet i jep lexuesit informacion rreth qëllimit të tij. Lexueshmëria është jashtëzakonisht e rëndësishme gjatë fazës së mirëmbajtjes.

Së fundi, efikasiteti mund të përcaktojë suksesin ose dështimin e teknologjisë së objektit në praktikë. Në mungesë të shtypjes statike për ekzekutim x.f(arg) mund të marrë çdo sasi kohe. Arsyeja për këtë është se në kohën e ekzekutimit, pa gjetur f në klasën e synuar bazë x, kërkimi do të vazhdojë me pasardhësit e tij dhe kjo është rruga e duhur drejt joefikasitetit. Ju mund ta lehtësoni problemin duke përmirësuar kërkimin e një komponenti në hierarki. Autorët e Vetë kanë bërë shumë punë në përpjekjen për të gjeneruar kodin më të mirë për një gjuhë të shtypur në mënyrë dinamike. Por ishte shtypja statike që lejoi një produkt të tillë OO të afrohej ose të barazohej në efikasitet me softuerin tradicional.

Çelësi i shtypjes statike është ideja e shprehur tashmë se përpiluesi që gjeneron kodin për konstruktin x.f(arg), njeh llojin x. Për shkak të polimorfizmit, nuk është e mundur të përcaktohet pa mëdyshje versioni i duhur i komponentit f. Por deklarata ngushton grupin e llojeve të mundshme, duke i lejuar përpiluesit të ndërtojë një tabelë që ofron qasje në të saktën f me kosto minimale, - me konstante të kufizuar vështirësia e aksesit. U kryen optimizime shtesë lidhje statike Dhe rreshtim- gjithashtu bëhen më të lehta nga shtypja statike, duke eliminuar plotësisht shpenzimet e sipërme aty ku zbatohet.

Rasti për shtypje dinamike

Pavarësisht gjithë kësaj, shtypja dinamike nuk i humbet ndjekësit e saj, veçanërisht në mesin e programuesve të Smalltalk. Argumentet e tyre bazohen kryesisht në realizmin e diskutuar më sipër. Ata besojnë se shtypja statike është shumë kufizuese, duke i penguar ata të shprehin lirshëm idetë e tyre krijuese, ndonjëherë duke e quajtur atë një "rrip dëlirësie".

Dikush mund të pajtohet me një arsyetim të tillë, por vetëm për gjuhët e shtypura në mënyrë statike që nuk mbështesin një numër karakteristikash. Vlen të përmendet se të gjitha konceptet që lidhen me konceptin e llojit dhe të prezantuara në leksionet e mëparshme janë të nevojshme - refuzimi i ndonjërit prej tyre është i mbushur me kufizime serioze, dhe futja e tyre, përkundrazi, i jep veprimeve tona fleksibilitet dhe na jep mundësia për të shijuar plotësisht prakticitetin e shtypjes statike.

Shtypja: komponentët e suksesit

Cilat janë mekanizmat për shtypjen realiste statike? Të gjitha ato u prezantuan në ligjëratat e mëparshme, prandaj mund t'i kujtojmë vetëm shkurtimisht. Renditja e tyre së bashku tregon qëndrueshmërinë dhe fuqinë e lidhjes së tyre.

Sistemi ynë i tipit bazohet tërësisht në koncept klasës. Edhe lloje të tilla bazë si I PLOTË, dhe për këtë arsye nuk kemi nevojë për rregulla të veçanta për përshkrimin e llojeve të paracaktuara. (Kjo është vendi ku shënimi ynë ndryshon nga gjuhët "hibride" si Object Pascal, Java dhe C++, të cilat kombinojnë sistemet e tipit të gjuhëve të vjetra me teknologjinë e objekteve të bazuara në klasë.)

Llojet e zgjeruara na jep më shumë fleksibilitet duke lejuar lloje, vlerat e të cilave tregojnë objekte, si dhe lloje, vlerat e të cilave tregojnë referenca.

Fjala vendimtare në krijimin e një sistemi të tipit fleksibël i përket trashëgimisë dhe konceptin përkatës pajtueshmërinë. Kjo kapërcen kufizimin kryesor të gjuhëve klasike të shtypura, për shembull, Pascal dhe Ada, në të cilat operatori x:=y kërkon që lloji x Dhe y ishte e njejta. Ky rregull është shumë i rreptë: ndalon përdorimin e subjekteve që mund të tregojnë objekte të llojeve të lidhura ( LLOGARIA E KURSIMEVE Dhe CHECKING_LLOGARIA). Kur trashëgojmë, ne kërkojmë vetëm përputhshmëri të tipit y me llojin x, Për shembull, x ka lloj LLOGARI, y- LLOGARIA E KURSIMEVE, dhe klasa e dytë është trashëgimtare e së parës.

Në praktikë, një gjuhë e shtypur në mënyrë statike ka nevojë për mbështetje trashëgimi e shumëfishtë. Ka akuza të njohura themelore kundër shtypjes statike se nuk lejon që objektet të interpretohen ndryshe. Po, objekti DOKUMENT(dokumenti) mund të transmetohet përmes një rrjeti, dhe për këtë arsye kërkon komponentë të lidhur me llojin MESAZH(mesazh). Por kjo kritikë është e vërtetë vetëm për gjuhët e kufizuara në një trashëgimi të vetme.

Oriz. 17.2. Trashëgimia e shumëfishtë

Shkathtësi nevojiten, për shembull, për të përshkruar strukturat fleksibël por të sigurt të të dhënave të kontejnerëve (për shembull LISTA e klasës [G] ...). Pa këtë mekanizëm, shtypja statike do të kërkonte deklarimin e klasave të ndryshme për listat që ndryshojnë në llojin e elementeve.

Në disa raste, kërkohet shkathtësi limit, i cili ju lejon të përdorni operacione që zbatohen vetëm për entitetet e tipit gjenerik. Nëse klasa gjenerike LIST E RRENDOSUR mbështet renditjen, kërkon entitete të llojit G, Ku G- parametri gjenerik, prania e një operacioni krahasimi. Kjo arrihet duke u lidhur me G klasa që specifikon një kufizim të përgjithshëm - I KRAHASUAR:


klasa SORTABLE_LIST ...

Çdo parametër gjenerik aktual LIST E RRENDOSUR duhet të jetë një pasardhës i klasës I KRAHASUAR, duke pasur komponentin e kërkuar.

Një mekanizëm tjetër i detyrueshëm është përpjekje për detyrë- organizon aksesin në ato objekte, llojin e të cilëve softueri nuk e kontrollon. Nëse yështë një objekt i bazës së të dhënave ose një objekt i marrë në rrjet, pastaj operatori x ?= y do të përvetësojë x kuptimi y, Nëse yështë i një lloji të përputhshëm, ose, nëse nuk është, do të japë x kuptimi E pavlefshme.

Deklarata të lidhura, si pjesë e idesë "Dizajni sipas kontratës", me klasat dhe përbërësit e tyre në formën e parakushteve, kushteve pas dhe invarianteve të klasës, bëjnë të mundur përshkrimin e kufizimeve semantike që nuk mbulohen nga specifikimi i tipit. Gjuhët si Pascal dhe Ada kanë lloje të diapazonit që mund të kufizojnë vlerat e entitetit, të themi, nga 10 në 20, por ju nuk do të jeni në gjendje t'i përdorni ato për të siguruar që vlera i ishte negative, gjithmonë dyfishi j. Klasat e pandryshuara vijnë në shpëtim, të krijuar për të pasqyruar me saktësi kufizimet e vendosura, pavarësisht sa komplekse mund të jenë ato.

Reklamat e gozhduara janë të nevojshme për të shmangur një ortek të dyfishimit të kodit në praktikë. Duke shpallur y: si x, ju merrni një garanci se y do të ndryshojë pas çdo deklarate të përsëritur të llojit x nga një pasardhës. Pa këtë mekanizëm, zhvilluesit do të rideklaronin vazhdimisht në një përpjekje për të ruajtur konsistencën midis llojeve të ndryshme.

Deklaratat ngjitëse janë një rast i veçantë i mekanizmit të fundit gjuhësor që ne kërkojmë - kovarianca, për të cilën do të diskutojmë në detaje më vonë.

Kur zhvilloni sisteme softuerike, në fakt, është e nevojshme një pronë më shumë e natyrshme në vetë mjedisin e zhvillimit - rikompilim i shpejtë në rritje. Kur shkruani ose modifikoni një sistem, dëshironi të shihni sa më shpejt efektin e ndryshimeve. Me shtypjen statike, duhet t'i jepni kohë përpiluesit për të kontrolluar dyfish llojet. Rutinat tradicionale të përpilimit kërkojnë ripërkthimin e të gjithë sistemit (dhe asambletë e saj), dhe ky proces mund të jetë shumë i gjatë, veçanërisht kur kaloni në sisteme në shkallë më të madhe. Ky fenomen u bë një argument në favor interpretuese sistemet, të tilla si mjediset e hershme Lisp ose Smalltalk, e drejtuan sistemin me pak ose aspak përpunim dhe pa kontroll të llojit. Ky argument tani është harruar. Një përpilues i mirë modern përcakton se si ka ndryshuar kodi që nga përpilimi i fundit dhe përpunon vetëm ndryshimet që gjen.

"A është foshnja e shtypur"?

Qellimi jone - i rreptë shtypja statike. Kjo është arsyeja pse ne duhet të shmangim çdo boshllëk në "lojën tonë sipas rregullave", të paktën duke i identifikuar me saktësi nëse ekzistojnë.

Hapësira më e zakonshme në gjuhët e shtypura në mënyrë statike është prania e konvertimeve që ndryshojnë llojin e një entiteti. Në C dhe gjuhët e derivateve të saj, ato quhen "tip casting" ose casting. Regjistro (OTHER_TYPE) x tregon se vlera x perceptohet nga përpiluesi se ka një tip OTHER_TYPE, subjekt i disa kufizimeve për llojet e mundshme.

Mekanizma të tillë anashkalojnë kufizimet e kontrollit të tipit. Casting është i zakonshëm në programimin C, duke përfshirë dialektin ANSI C. Edhe në C++, casting-i i tipit, megjithëse më pak i zakonshëm, mbetet i zakonshëm dhe ndoshta i nevojshëm.

Përmbushja e rregullave të shtypjes statike nuk është aq e lehtë nëse ato mund të anashkalohen në çdo kohë duke hedhur.

Shtypja dhe Lidhja

Edhe pse si lexues i këtij libri me siguri do të dalloni shtypjen statike nga statike detyruese, ka njerëz që nuk mund ta bëjnë këtë. Kjo mund të jetë pjesërisht për shkak të ndikimit të Smalltalk, i cili mbron një qasje dinamike për të dy problemet dhe mund të çojë në keqkuptimin se ata kanë të njëjtën zgjidhje. (Në librin tonë, ne argumentojmë se për të krijuar programe të besueshme dhe fleksibël, është e dëshirueshme të kombinohen shtypja statike dhe lidhja dinamike.)

Si shtypja ashtu edhe lidhja merren me semantikën e Konstruksionit Bazë x.f(arg), por përgjigjuni dy pyetjeve të ndryshme:

Shtypja dhe Lidhja

[x]. Pyetje duke shtypur: kur duhet të dimë me siguri se një operacion korrespondon me f, i zbatueshëm për një objekt të bashkangjitur një njësie ekonomike x(me parametër arg)?

[x]. Pyetje lidhëse: Kur duhet të dimë se çfarë operacioni fillon një telefonatë e caktuar?

Shtypja i përgjigjet pyetjes së disponueshmërisë të paktën një operacionet, lidhja është përgjegjëse për përzgjedhjen e nevojshme.

Brenda qasjes së objektit:

[x]. problemi që lind me shtypjen lidhet me polimorfizëm: sepse xgjatë ekzekutimit mund të përfaqësojnë objekte të disa llojeve të ndryshme, duhet të jemi të sigurt se operacioni që përfaqëson f, në dispozicion në secilin prej këtyre rasteve;

[x]. problem detyrues i shkaktuar njoftime të përsëritura: Meqenëse një klasë mund të ndryshojë komponentët e trashëguar, mund të ketë dy ose më shumë operacione që pretendojnë se përfaqësojnë f në këtë thirrje.

Të dy problemet mund të zgjidhen si në mënyrë dinamike ashtu edhe statike. Gjuhët ekzistuese ofrojnë të katër zgjidhjet.

[x]. Një numër i gjuhëve jo-objekte, të themi Pascal dhe Ada, zbatojnë të dy shtypjen statike dhe lidhjen statike. Çdo entitet paraqet objekte të vetëm një lloji, të përcaktuar në mënyrë statike. Kjo siguron besueshmërinë e zgjidhjes, çmimi për të cilin është fleksibiliteti i saj.

[x]. Smalltalk dhe gjuhë të tjera të orientuara nga objekti përmbajnë veçori për lidhje dinamike dhe shtypje dinamike. Në këtë rast, përparësi i jepet fleksibilitetit në kurriz të besueshmërisë së gjuhës.

[x]. Disa gjuhë jo-objekte mbështesin shtypjen dinamike dhe lidhjen statike. Midis tyre janë gjuhët e asamblesë dhe një numër i gjuhëve të shkrimit.

[x]. Idetë e shtypjes statike dhe lidhjes dinamike janë mishëruar në shënimin e propozuar në këtë libër.

Le të vëmë re origjinalitetin e gjuhës C++, e cila mbështet shtypjen statike, megjithëse jo e rreptë për shkak të pranisë së transmetimit të tipit, lidhjes statike (sipas parazgjedhjes), lidhjes dinamike me tregues të qartë të virtuales ( Virtual) reklamat.

Arsyeja për zgjedhjen e shtypjes statike dhe lidhjes dinamike është e qartë. Pyetja e parë është: "Kur do ta dimë se ekzistojnë komponentët?" - sugjeron një përgjigje statike: " Sa më herët aq më mirë", që do të thotë: në kohën e përpilimit. Pyetja e dytë: "Cilin komponent duhet të përdor?" kërkon një përgjigje dinamike: " ai që ju nevojitet", - që korrespondon me llojin dinamik të objektit të përcaktuar në kohën e ekzekutimit. Kjo është e vetmja zgjidhje e pranueshme nëse lidhja statike dhe dinamike prodhojnë rezultate të ndryshme.

Shembulli i mëposhtëm i një hierarkie të trashëgimisë do të ndihmojë në sqarimin e këtyre koncepteve:

Oriz. 17.3. Llojet e avionëve

Merrni parasysh sfidën:


my_aircraft.lower_landing_gear

Pyetja e shtypjes: kur të siguroheni që do të ketë një komponent këtu veshjet e ulëta të uljes("pajisja e poshtme e uljes"), e zbatueshme për një objekt (për KOPTER nuk do të ekzistojë fare) Pyetje për lidhjen: cilin nga disa versione të mundshme të zgjedhim.

Lidhja statike do të thotë që ne shpërfillim llojin e objektit që bashkëngjitet dhe mbështetemi në deklaratën e entitetit. Si rezultat, kur kemi të bëjmë me Boeing 747-400, do të kërkojmë një version të projektuar për avionët e zakonshëm të serisë 747, dhe jo për modifikimin e tyre 747-400. Lidhja dinamike zbaton funksionimin e kërkuar nga objekti, dhe kjo është qasja e duhur.

Me shtypjen statike, përpiluesi nuk do të refuzojë një thirrje nëse mund të garantohet se kur programi ekzekutohet kundër entitetit my_avion objekti i furnizuar me komponentin që i korrespondon veshjet e ulëta të uljes. Teknika bazë për marrjen e garancive është e thjeshtë: me njoftim të detyrueshëm my_avion Klasa bazë e llojit të saj kërkohet të përfshijë një komponent të tillë. Kjo është arsyeja pse my_avion nuk mund të deklarohet si AVION, pasi kjo e fundit nuk ka veshjet e ulëta të uljes në këtë nivel; Helikopterët, të paktën në shembullin tonë, nuk dinë të ulin mjetet e uljes. Nëse subjektin e deklarojmë si AEROPLAN, - një klasë që përmban komponentin e kërkuar - gjithçka do të jetë mirë.

Shtypja dinamike në stilin Smalltalk kërkon që ju të prisni për thirrjen dhe në momentin e ekzekutimit të saj, të kontrolloni praninë e komponentit të dëshiruar. Kjo sjellje është e mundur për prototipet dhe zhvillimet eksperimentale, por është e papranueshme për sistemet industriale - në momentin e fluturimit është tepër vonë për të pyetur nëse keni pajisje uljeje.

Kovarianca dhe fshehja e fëmijëve

Nëse bota do të ishte e thjeshtë, atëherë biseda rreth shtypjes mund të përfundonte. Ne kemi identifikuar qëllimet dhe përfitimet e shtypjes statike, kemi ekzaminuar kufizimet që sistemet e tipit realist duhet të plotësojnë dhe kemi verifikuar që metodat e propozuara të shtypjes plotësojnë kriteret tona.

Por bota nuk është e thjeshtë. Kombinimi i shtypjes statike me disa kërkesa të inxhinierisë softuerike krijon probleme që janë më komplekse nga sa duket. Dy mekanizma shkaktojnë probleme: kovarianca- ndryshimi i llojeve të parametrave gjatë ripërcaktimit, pasardhës i fshehur- aftësia e një klase pasardhëse për të kufizuar statusin e eksportit të komponentëve të trashëguar.

Kovarianca

Çfarë ndodh me argumentet e një komponenti kur lloji i tij anashkalohet? Ky është një problem madhor dhe ne kemi parë tashmë një sërë shembujsh të manifestimit të tij: pajisje dhe printera, lista të lidhura të vetme dhe dyfish, etj. (shih seksionet 16.6, 16.7).

Këtu është një shembull tjetër për të ndihmuar në sqarimin e natyrës së problemit. Dhe megjithëse është larg realitetit dhe metaforik, afërsia e tij me skemat softuerike është e dukshme. Përveç kësaj, kur e analizojmë, shpesh do t'u kthehemi problemeve nga praktika.

Le të imagjinojmë një ekip skijimi universitar që përgatitet për kampionatin. Klasa VAJZE përfshin skiatorë që garojnë në ekipin e femrave, DJALI- skiatorë. Një numër pjesëmarrësish nga të dyja skuadrat janë renditur duke treguar rezultate të mira në garat e mëparshme. Kjo është e rëndësishme për ta, sepse tani ata do të vrapojnë të parët, duke fituar një avantazh ndaj të tjerëve. (Ky rregull, i cili u jep privilegje tashmë të privilegjuarve, është ndoshta ajo që e bën sllallomin dhe skijimin në vend kaq tërheqës në sytë e shumë njerëzve, duke qenë një metaforë e mirë për vetë jetën.) Pra, kemi dy klasa të reja: RANKED_GIRL Dhe RANKED_BOY.

Oriz. 17.4. Klasifikimi i skiatorëve

Një numër dhomash janë rezervuar për qëndrimin e sportistëve: vetëm për burra, vetëm për vajza, vetëm për vajza fituese. Për të shfaqur këtë ne përdorim një hierarki paralele të klasës: DHOMA, GIRL_ROOM Dhe RANKED_GIRL_ROOM.

Këtu është një skicë e klasës SKIER:


- Shok dhome.
...Përbërës të tjerë të mundshëm të hequr në këtë dhe klasat pasuese...

Ne jemi të interesuar për dy komponentë: atributin shok dhome dhe procedurën ndajnë, e cila e "vendos" këtë skiator në të njëjtën dhomë me skiatorin aktual:


Me rastin e deklarimit të një subjekti tjera ju mund të refuzoni llojin SKIER në favor të tipit fiks si shoku i dhomës(ose si Rryma Për shok dhome Dhe tjera njëkohësisht). Por le të harrojmë për një moment detyrat e tipit (do t'u kthehemi atyre më vonë) dhe të shohim problemin e kovariancës në formën e tij origjinale.

Si të prezantohet mbivendosja e tipit? Rregullat kërkojnë akomodim të veçantë për djemtë dhe vajzat, fituesit dhe pjesëmarrësit e tjerë. Për të zgjidhur këtë problem, kur e tejkalojmë, le të ndryshojmë llojin e komponentit shok dhome, siç tregohet më poshtë (këtu dhe më poshtë, nënvizohen elementët e anashkaluar).


- Shok dhome.

Le të ripërcaktojmë, në përputhje me rrethanat, argumentin e procedurës ndajnë. Një version më i plotë i klasës tani duket kështu:


- Shok dhome.
-- Zgjidh një tjetër si fqinj.

Në mënyrë të ngjashme, ju duhet të ndryshoni të gjitha të krijuara nga SKIER klasa (aktualisht nuk përdorim lidhjen e tipit). Si rezultat, ne kemi një hierarki:

Oriz. 17.5. Hierarkia e pjesëmarrësve dhe ripërcaktimet

Meqenëse trashëgimia është një specializim, rregullat e tipit kërkojnë që kur tejkalohet rezultati i një komponenti, në këtë rast shok dhome, lloji i ri ishte pasardhës i atij origjinali. E njëjta gjë vlen edhe për tejkalimin e llojit të argumentit tjera nënprogramet ndajnë. Kjo strategji, siç e dimë, quhet kovariancë, ku parashtesa "co" tregon një ndryshim të përbashkët në llojet e parametrit dhe rezultatit. Strategjia e kundërt quhet kontravariancë.

Të gjithë shembujt tanë ofrojnë dëshmi bindëse të nevojës praktike për kovariancë.

[x]. Elementi i listës i lidhur vetëm E LIDHSHME duhet të shoqërohet me një element tjetër të ngjashëm, dhe shembullin BI_LINKE MUNDSHME- me dikë si ai. Kovarianti do të duhet të anashkalohet dhe argumenti të futet vë_drejtë.

[x].Çdo nënprogram në LINKED_LIST me një argument si E LIDHSHME kur lëvizni në TWO_WAY_LIST do të kërkojë një argument BI_LINKE MUNDSHME.

[x]. Procedura vendos_alternativ pranon PAJISJE-argumenti në klasë PAJISJE Dhe PRINTER-argument - në klasë PRINTER.

Ripërcaktimi i bashkëvariantit është bërë veçanërisht i përhapur sepse fshehja e informacionit çon në krijimin e procedurave të formës


-- Cakto atributin në v.

për të punuar me atribut lloji SOME_TYPE. Procedura të tilla janë natyrisht bashkëvariante, pasi çdo klasë që ndryshon llojin e një atributi duhet të ripërcaktojë argumentin në përputhje me rrethanat. set_attrib. Megjithëse shembujt e paraqitur përshtaten në një skemë, kovarianca është shumë më e përhapur. Mendoni, për shembull, një procedurë ose funksion që kryen lidhjen e listave të lidhura veçmas ( LINKED_LIST). Argumenti i tij duhet të ripërcaktohet si një listë e lidhur dyfish ( TWO_WAY_LIST). Operacioni universal i shtimit infix "+" pranon NUMERIK-argumenti në klasë NUMERIK, REAL- në klasë REAL Dhe I PLOTË- në klasë I PLOTË. Paralelisht hierarkitë e shërbimit telefonik me procedurën filloni në klasë PHONE_SERVICE mund të kërkojë një argument ADRESË, që përfaqëson adresën e pajtimtarit, (për faturim), ndërsa e njëjta procedurë në klasë CORPORATE_SERVICE do të kërkojë një argument si ADRESA_KORPORATE.

Oriz. 17.6. Shërbimet e Komunikimit

Po në lidhje me zgjidhjen kundërthënëse? Në shembullin e skiatorëve, do të nënkuptohej se nëse, lëvizja në klasë RANKED_GIRL, lloji i rezultatit shok dhome ripërcaktuar si RANKED_GIRL, pastaj, për shkak të kundërvarësisë, lloji i argumentit ndajnë mund të anashkalohet për të shtypur VAJZE ose SKIER. I vetmi lloj që nuk lejohet në një zgjidhje kundërthënëse është RANKED_GIRL! Mjaft për të ngjallur dyshimet më të këqija tek prindërit e vajzave.

Hierarkitë paralele

Për të mos lënë gur pa lëvizur, le të shqyrtojmë një variant të shembullit SKIER me dy hierarki paralele. Kjo do të na lejojë të simulojmë një situatë që tashmë është hasur në praktikë: TWO_WAY_LIST > LINKED_LIST Dhe BI_LINK MUNDSHME > LINK MUNDSHME; ose hierarki me sherbim telefonik PHONE_SERVICE.

Le të ketë një hierarki me një klasë DHOMA, pasardhës i të cilit është GIRL_ROOM(Klasa DJALI të hequra):

Oriz. 17.7. Skiatorë dhe dhoma

Klasat tona të skiatorëve në këtë hierarki paralele shok dhome Dhe ndajnë do të ketë komponentë të ngjashëm akomodimi (akomodimi) Dhe akomoduar (postim):


Përshkrimi: "Opsion i ri me hierarki paralele"
akomodoj (r: DHOMA) është ... kërkoj ... bëj

Këtu nevojiten edhe mbivendosjet e bashkëvarianteve: në klasë VAJZË 1 Si akomodimi, dhe argumenti i nënprogramit akomoduar duhet të zëvendësohet sipas llojit GIRL_ROOM, në klasë DJALI 1- lloji DHOMA_DJALI etj. (Mos harroni, ne jemi ende duke punuar pa ankorimin e tipit.) Ashtu si me shembullin e mëparshëm, kontravarianca nuk është e dobishme këtu.

Drejtësia e polimorfizmit

A nuk ka shembuj të mjaftueshëm për të mbështetur prakticitetin e kovariacionit? Pse dikush do të konsideronte kontravariancë që bie ndesh me atë që nevojitet në praktikë (përveç sjelljes së disa të rinjve)? Për ta kuptuar këtë, merrni parasysh problemet që lindin kur kombinohen polimorfizmi dhe strategjitë e kovariancës. Ardhja me një skemë sabotimi nuk është e vështirë, dhe ju mund ta keni krijuar tashmë një të tillë:


krijoj b; krijoni g;-- Krijo objekte BOY dhe GIRL.

Rezultati i sfidës së fundit, ndonëse mund të jetë i pëlqyeshëm për djemtë, është pikërisht ai që po përpiqeshim të parandalonim me mbizotërimin e tipit. Thirrni ndajnëçon në faktin se objekti DJALI, i njohur si b dhe falë polimorfizmit mori një pseudonim s lloji SKIER, bëhet fqinj i objektit VAJZE, i njohur si g. Mirëpo, sfida, edhe pse në kundërshtim me rregullat e hostelit, është mjaft korrekte në tekstin e programit, pasi ndajnë-komponent i eksportuar në përbërje SKIER, A VAJZE, lloji i argumentit g, i përputhshëm me SKIER, lloji i parametrit formal ndajnë.

Skema e hierarkisë paralele është po aq e thjeshtë: zëvendëso SKIERSKIER1, sfidë ndajnë- në Thirrje s.akomodoj (gr), Ku gr- lloji i entitetit GIRL_ROOM. Rezultati është i njëjtë.

Me një zgjidhje të kundërt, këto probleme nuk do të lindnin: specializimi i objektivit të thirrjes (në shembullin tonë s) do të kërkonte një përgjithësim të argumentit. Kontravarianca rezulton në një model më të thjeshtë matematikor të mekanizmit: trashëgimi - mbivendosje - polimorfizëm. Ky fakt përshkruhet në një sërë artikujsh teorikë që propozojnë këtë strategji. Argumenti nuk është shumë bindës, pasi, siç tregojnë shembujt tanë dhe botimet e tjera, kontravarianca nuk ka përdorim praktik.

Prandaj, pa u përpjekur për të vendosur veshje kontravariante në një trup bashkëvariant, duhet pranuar realiteti bashkëvariant dhe të kërkohen mënyra për të eliminuar efektin e padëshiruar.

Fshehja nga një fëmijë

Përpara se të kërkojmë një zgjidhje për problemin e kovariancës, le të shqyrtojmë një mekanizëm tjetër që, në kushtet e polimorfizmit, mund të çojë në shkelje të tipit. Fshehja e pasardhësve është aftësia e një klase për të mos eksportuar një komponent që rrjedh nga prindërit e saj.

Oriz. 17.8. Fshehja nga një fëmijë

Një shembull tipik është komponenti shtoj_kulm(shto kulm) të eksportuar sipas klasës POLIGONI, por e fshehur nga pasardhësi i saj DREJTKËNDËSH(për shkak të një shkeljeje të mundshme të invariantit - klasa dëshiron të mbetet një drejtkëndësh):


Shembull joprogramuesi: klasa "Struc" fsheh metodën "Fly" të marrë nga prindi "Zog".

Le ta marrim këtë skemë ashtu siç është për një moment dhe të pyesim nëse një kombinim i trashëgimisë dhe fshehjes është legjitim. Roli modelues i fshehjes, si kovarianca, cenohet nga truket e mundshme për shkak të polimorfizmit. Dhe këtu nuk është e vështirë të ndërtosh një shembull keqdashës që lejon, pavarësisht nga fshehja e komponentit, ta thërrasësh atë dhe të shtosh një kulm në drejtkëndësh:


krijoni r; -- Krijo një objekt DREJTËKËNDËSH.
p:= r; -- Detyrë polimorfike.

Që nga objekti r duke u fshehur nën esencë fq klasës POLIGONI, A shtoj_kulm komponent i eksportuar POLIGONI, pastaj sfida e saj në thelb fq e saktë. Si rezultat i ekzekutimit, një kulm tjetër do të shfaqet në drejtkëndësh, që do të thotë se do të krijohet një objekt i pavlefshëm.

Korrektësia e sistemeve dhe klasave

Për të diskutuar çështjet e kovariancës dhe fshehjes së fëmijëve, na duhen disa terma të rinj. Ne do të thërrasim klasë-vlefshme një sistem që plotëson tre rregullat e përshkrimit të tipit të dhëna në fillim të leksionit. Le t'i kujtojmë: çdo entitet ka llojin e vet; lloji i argumentit aktual duhet të jetë në përputhje me llojin e argumentit formal, një situatë e ngjashme me caktimin; fasulja që do të thirret duhet të deklarohet në klasën e vet dhe të eksportohet në klasën që përmban thirrjen.

Sistemi quhet sistem-i vlefshëm, nëse gjatë ekzekutimit të tij nuk ndodh shkelje e llojit.

Idealisht, të dy konceptet duhet të përkojnë. Megjithatë, ne kemi parë tashmë se një sistem i saktë sipas klasës në kushtet e trashëgimisë, kovariancës dhe fshehjes nga një pasardhës mund të mos jetë i saktë sipas sistemit. Le ta quajmë këtë gabim shkelje e gabimit të vlefshmërisë së sistemit.

Aspekti praktik

Thjeshtësia e problemit krijon një lloj paradoksi: një fillestar kërkues do të ndërtojë një kundërshembull brenda pak minutash; në praktikën reale, gabimet në korrektësinë e klasës së sistemeve ndodhin çdo ditë, por shkelje të korrektësisë së sistemit, madje edhe në masë të madhe, shumë. -projektet vjetore, ndodhin jashtëzakonisht rrallë.

Sidoqoftë, kjo nuk na lejon t'i injorojmë ato, dhe për këtë arsye ne fillojmë të studiojmë tre mënyra të mundshme për të zgjidhur këtë problem.

Më pas, do të prekim aspekte shumë delikate dhe jo aq shpesh të dukshme të qasjes së objektit. Nëse po e lexoni librin për herë të parë, mund të kaloni pjesët e mbetura të këtij leksioni. Nëse keni marrë vetëm kohët e fundit teknologjinë OO, do ta kuptoni më mirë këtë material pasi të studioni leksionet 1-11 të kursit "Bazat e dizajnit të orientuar nga objekti", kushtuar metodologjisë së trashëgimisë, dhe veçanërisht leksionin 6 të "Bazat e objektit- Kursi i Orientuar Design”, kushtuar trashëgimisë së metodologjisë.

Korrektësia e sistemeve: përafrimi i parë

Le të përqendrohemi së pari në problemin e kovariancës, më i rëndësishmi nga të dy të konsideruarit. Ekziston një literaturë e gjerë kushtuar kësaj teme, duke ofruar një sërë zgjidhjesh të ndryshme.

Kontravarianca dhe mosvarianca

Kontravarianca eliminon problemet teorike që lidhen me shkeljen e korrektësisë së sistemit. Megjithatë, kjo e humbet realizmin e sistemit të tipit; për këtë arsye, nuk ka nevojë të shqyrtohet më tej kjo qasje.

Origjinaliteti i gjuhës C++ është se ajo përdor strategjinë novarianca, duke ju penguar të ndryshoni llojin e argumenteve në rutinat e anashkaluara! Nëse C++ do të ishte një gjuhë e shtypur fort, sistemi i tipit të saj do të ishte i vështirë për t'u përdorur. Zgjidhja më e thjeshtë e problemit në këtë gjuhë, si dhe anashkalimi i kufizimeve të tjera të C++ (për shembull, mungesa e universalitetit të kufizuar), është përdorimi i hedhjes - lloji i hedhjes, i cili ju lejon të injoroni plotësisht mekanizmin ekzistues të shtypjes. Kjo zgjidhje nuk duket tërheqëse. Megjithatë, vini re se një numër i propozimeve të diskutuara më poshtë do të mbështeten në jovarianca, kuptimi i së cilës do të jepet nga futja e mekanizmave të rinj për të punuar me llojet në vend të ripërcaktimit bashkëvariant.

Përdorimi i parametrave gjenerikë

Universaliteti është në zemër të një ideje interesante të shprehur fillimisht nga Franz Weber. Le të deklarojmë klasën SKIER1, duke kufizuar universalizimin e parametrit gjenerik në klasë DHOMA:


tipar i klasës SKIER1
akomodoj (r: G) është ... kërkoj ... bëj akomodim:= r fund

Pastaj klasa VAJZË 1 do të jetë trashëgimtari SKIER1 etj. E njëjta teknikë, sado e çuditshme të duket në shikim të parë, mund të përdoret në mungesë të një hierarkie paralele: Klasa SKIER.

Kjo qasje zgjidh problemin e kovariancës. Çdo përdorim i klasës duhet të specifikojë parametrin aktual të përgjithshëm DHOMA ose GIRL_ROOM, kështu që një kombinim i gabuar thjesht bëhet i pamundur. Gjuha bëhet pa variant dhe sistemi plotëson plotësisht nevojat e kovariancës falë parametrave gjenerikë.

Fatkeqësisht, kjo teknikë nuk është e përshtatshme si një zgjidhje e përgjithshme, sepse ajo çon në një listë në rritje të parametrave gjenerikë, një për çdo lloj argumenti të mundshëm bashkëvariant. Më keq, shtimi i një nënprogrami kovariant me një argument, lloji i të cilit nuk është në listë, do të kërkojë shtimin e një parametri të përgjithshëm të klasës, dhe për rrjedhojë të ndryshojë ndërfaqen e klasës, duke shkaktuar ndryshime te të gjithë klientët e klasës, gjë që është e papranueshme.

Variabla tipike

Një numër autorësh, duke përfshirë Kim Bruce, David Shang dhe Tony Simons, kanë propozuar një zgjidhje të bazuar në variablat e tipit, vlerat e të cilave janë tipe. Ideja e tyre është e thjeshtë:

[x]. në vend të zëvendësimeve të bashkëvarianteve, lejo deklaratat e tipit që përdorin variabla të tipit;

[x]. të zgjerojë rregullat e përputhshmërisë së tipit për të menaxhuar variabla të tillë;

[x]. ofrojnë mundësinë për të caktuar llojet e gjuhëve si vlera për variablat e tipit.

Lexuesit mund të gjejnë një prezantim të detajuar të këtyre ideve në një numër artikujsh mbi këtë temë, si dhe në botimet e Cardelli, Castagna, Weber dhe të tjerë. Ju mund ta filloni studimin e çështjes nga burimet e treguara në shënimet bibliografike për këtë leksion. . Ne nuk do të merremi me këtë problem, dhe ja pse.

[x]. Një mekanizëm variabël i implementuar siç duhet bën pjesë në kategorinë që lejon një lloj të përdoret pa e specifikuar plotësisht atë. E njëjta kategori përfshin shkathtësinë dhe ngjitjen e reklamave. Ky mekanizëm mund të zëvendësojë mekanizmat e tjerë të kësaj kategorie. Kjo fillimisht mund të interpretohet në favor të variablave të tipit, por rezultati mund të jetë katastrofik, pasi nuk është e qartë se ky mekanizëm gjithëpërfshirës mund të trajtojë të gjitha detyrat me lehtësinë dhe thjeshtësinë që është e natyrshme në universalitetin dhe ankorimin e tipit.

[x]. Le të supozojmë se është zhvilluar një mekanizëm variabël i tipit që mund të kapërcejë problemet e kombinimit të kovariancës dhe polimorfizmit (duke injoruar ende problemin e fshehjes së fëmijëve). Atëherë do të kërkohet zhvilluesi i klasës intuitë e jashtëzakonshme në mënyrë që të vendoset paraprakisht se cili nga komponentët do të jetë i disponueshëm për tipet mbizotëruese në klasat e gjeneruara dhe cilët jo. Më poshtë do të diskutojmë këtë problem, i cili shfaqet në praktikën e krijimit të programeve dhe, mjerisht, hedh dyshime mbi zbatueshmërinë e shumë skemave teorike.

Kjo na detyron t'u kthehemi mekanizmave të diskutuar tashmë: universaliteti i kufizuar dhe i pakufizuar, ankorimi i tipit dhe, natyrisht, trashëgimia.

Duke u mbështetur në ankorimin e tipit

Ne do të gjejmë një zgjidhje pothuajse të gatshme për problemin e kovariancës duke i hedhur një vështrim më të afërt mekanizmit të reklamave ngjitëse për të cilat dimë.

Gjatë përshkrimit të klasave SKIER Dhe SKIER1 Nuk mund të mos tundoheshe të përdorësh reklama të fiksuara për të hequr qafe shumë anashkalime. Ankorimi është një mekanizëm tipik kovariant. Kështu do të dukej shembulli ynë (të gjitha ndryshimet janë të nënvizuara):


share (të tjera: si Aktual) është ... kërkoj ... bëj
akomodoj (r: si akomodim) është ... kërkoj ... bëj

Tani pasardhësit mund të largohen nga klasa SKIER nuk ka ndryshime, por SKIER1 ata do të duhet vetëm të anashkalojnë atributin akomodimi. Subjektet e gozhduara: atribut shok dhome dhe argumentet e nënrutinës ndajnë Dhe akomoduar- do të ndryshojë automatikisht. Kjo thjeshton shumë punën dhe përforcon faktin se në mungesë të fiksimit (ose një mekanizmi tjetër të ngjashëm, siç janë variablat e tipit), është e pamundur të shkruhet një produkt softuerësh i orientuar nga objekti me shtypje realiste.

Por a ishte e mundur të eliminoheshin shkeljet e korrektësisë së sistemit? Jo! Ne ende mund ta tejkalojmë kontrollin e tipit duke bërë caktime polimorfike që shkaktojnë shkelje të korrektësisë së sistemit.

Megjithatë, versionet origjinale të shembujve do të refuzohen. Le te jete:


krijoj b;krijoj g;-- Krijo objekte BOY dhe GIRL.
s:= b; -- Detyrë polimorfike.

Argumenti g, transmetohet ndajnë, tani është e pasaktë sepse kërkon një objekt të llojit si s, dhe klasa VAJZE nuk është i pajtueshëm me këtë lloj sepse, sipas rregullit të tipit të ankoruar, asnjë lloj nuk është i pajtueshëm me si s përveç vetes së tij.

Megjithatë, ne nuk do të jemi të lumtur për një kohë të gjatë. Nga ana tjetër, ky rregull thotë se si s në përputhje me llojin s. Kjo nënkupton përdorimin e polimorfizmit jo vetëm të objektit s, por edhe parametri g, ne mund të anashkalojmë përsëri sistemin e kontrollit të tipit:


s: SKIER; b: DJALI; g: si s; aktuale_g: VAJZË;
krijoj b; krijo actual_g -- Krijo objekte BOY dhe GIRL.
s:= aktuale_g; g:= s -- Përdorni s për të bashkangjitur g me GIRL.
s:= b -- Detyrë polimorfike.

Si rezultat, thirrja e paligjshme kalon.

Ka një rrugëdalje. Nëse jemi seriozë për përdorimin e fiksimit të deklaratave si të vetmin mekanizëm të kovariancës, atëherë mund të shpëtojmë nga shkeljet e korrektësisë së sistemit duke ndaluar plotësisht polimorfizmin e entiteteve të fiksuara. Kjo do të kërkojë një ndryshim në gjuhë: le të prezantojmë një fjalë kyçe të re spirancë(ne kemi nevojë për këtë ndërtim hipotetik vetëm për përdorim në këtë diskutim):


Le të lejojmë deklaratat e formularit si s vetem kur s përshkruar si spirancë. Ne do të ndryshojmë rregullat e përputhshmërisë për të siguruar: s dhe elemente si si s mund t'i bashkëngjiten vetëm njëra-tjetrës (në detyra ose kalim argumentesh).

Me këtë qasje, ne heqim nga gjuha aftësinë për të anashkaluar llojin e çdo argumenti nënrutinë. Përveç kësaj, ne mund të parandalojmë që lloji i rezultatit të anashkalohet, por kjo nuk është e nevojshme. Mundësia për të ripërcaktuar llojin e atributit, natyrisht, mbetet. Të gjitha Mbështetja e llojit të argumentit tani do të bëhet në mënyrë implicite përmes një mekanizmi ngjitës të shkaktuar nga kovarianca. Ku, me qasjen e mëparshme, klasa D ripërcaktoi komponentin e trashëguar si:


kurse klasa C- prind D dukej


Ku Y korrespondonte X, atëherë tani anashkalimi i komponentit r do të duket kështu:


Mbetet vetëm në klasë D lloji i anashkalimit spiranca jote.

Këtë zgjidhje për problemin e kovariancës - polimorfizëm do ta quajmë qasje Ankorimi. Do të ishte më e saktë të thoshim: "Kovariacion vetëm përmes konsolidimit". Karakteristikat e qasjes janë tërheqëse:

[x]. Përforcimi bazohet në idenë e ndarjes së rreptë bashkëvariant dhe elemente potencialisht polimorfikë (ose shkurt polimorfikë). Të gjitha subjektet e deklaruara si spirancë ose si disa_spirancë bashkëvariant; të tjerat janë polimorfike. Në secilën nga dy kategoritë, çdo adjunsion lejohet, por nuk ka asnjë entitet apo shprehje që shkel kufirin. Ju nuk mund, për shembull, t'i caktoni një burim polimorfik një objektivi kovariant.

[x]. Kjo zgjidhje e thjeshtë dhe elegante është e lehtë për t'u shpjeguar edhe për fillestarët.

[x]. Ai eliminon plotësisht mundësinë e shkeljes së korrektësisë së sistemit në sistemet e ndërtuara në mënyrë bashkëvariante.

[x]. Ai ruan themelin konceptual të hedhur më sipër, duke përfshirë konceptet e universalitetit të kufizuar dhe të pakufizuar. (Si rezultat, kjo zgjidhje, për mendimin tim, preferohet nga variablat standarde që zëvendësojnë mekanizmat e kovariancës dhe universalitetit, të krijuar për të zgjidhur probleme të ndryshme praktike.)

[x]. Kërkon një ndryshim të vogël në gjuhë - duke shtuar një fjalë kyçe të vetme të pasqyruar në rregullin e përputhjes - dhe nuk përfshin ndonjë vështirësi të rëndësishme zbatimi.

[x].Është realiste (të paktën në teori): çdo sistem i mundshëm më parë mund të rishkruhet duke zëvendësuar mbivendosjet e bashkëvarianteve me rideklarime ngjitëse. Është e vërtetë që disa bashkime do të jenë të pavlefshme si rezultat, por ato korrespondojnë me raste që mund të çojnë në shkelje të tipit dhe për këtë arsye duhet të zëvendësohen me përpjekje për caktimin dhe të trajtohen në kohën e ekzekutimit.

Duket se diskutimi mund të përfundojë këtu. Pra, pse nuk jemi plotësisht të kënaqur me qasjen e Përforcimit? Së pari, ne ende nuk e kemi prekur çështjen e fshehjes nga një fëmijë. Për më tepër, arsyeja kryesore për vazhdimin e diskutimit është problemi i shprehur tashmë në përmendjen e shkurtër të variablave të tipit. Ndarja e sferave të ndikimit në pjesë polimorfike dhe bashkëvariante është disi e ngjashme me rezultatin e konferencës së Jaltës. Supozon se projektuesi i një klase ka intuitën e jashtëzakonshme që ai është në gjendje, për çdo entitet që prezanton, veçanërisht për çdo argument, të zgjedhë njëherë e përgjithmonë një nga dy mundësitë:

[x]. Një entitet është potencialisht polimorfik: tani ose më vonë (duke kaluar parametra ose me caktim) mund t'i bashkëngjitet një objekti lloji i të cilit është i ndryshëm nga ai i deklaruar. Lloji origjinal i entitetit nuk mund të ndryshohet nga asnjë pasardhës i klasës.

[x]. Një njësi ekonomike është subjekt i ripërcaktimit të llojit, domethënë ose është fiks ose është në vetvete një element mbështetës.

Por si mund ta parashikojë një zhvillues gjithë këtë? E gjithë atraktiviteti i metodës OO, i shprehur kryesisht në parimin e hapur-mbyllur, lidhet pikërisht me mundësinë e ndryshimeve që ne kemi të drejtë të bëjmë në punën e bërë më parë, si dhe me faktin që zhvilluesi i zgjidhjeve universale Jo duhet të ketë mençuri të pafund, të kuptojë se si produkti i tij mund të përshtatet me nevojat e pasardhësve të tij.

Me këtë qasje, mbivendosja e tipit dhe fshehja e fëmijëve janë një lloj "valvule sigurie" që bën të mundur ripërdorimin e një klase ekzistuese, pothuajse të përshtatshme për arritjen e qëllimeve tona:

[x]. Duke përdorur mbivendosjen e tipit, ne mund të ndryshojmë deklaratat në klasën e prejardhur pa ndikuar në origjinalin. Në këtë rast, një zgjidhje thjesht bashkëvariante do të kërkojë redaktimin e origjinalit duke përdorur transformimet e përshkruara.

[x]. Fshehja nga një fëmijë mbron nga shumë dështime kur krijon një klasë. Dikush mund të kritikojë një projekt në të cilin DREJTKËNDËSH, duke përdorur faktin se aiështë pasardhës POLIGONI, përpiqet të shtojë një kulm. Në vend të kësaj, mund të propozohet një strukturë trashëgimie në të cilën figurat me një numër fiks kulmesh janë të ndara nga të gjitha të tjerat dhe problemi nuk do të lindte. Megjithatë, gjatë zhvillimit të strukturave të trashëgimisë, ato në të cilat nuk ka përjashtimet taksonomike. Por a mund të eliminohen plotësisht? Kur diskutojmë kufizimet e eksportit në një leksion të mëvonshëm, do të shohim se kjo është e pamundur për dy arsye. E para është prania e kritereve konkurruese të klasifikimit. Së dyti, ekziston mundësia që zhvilluesi të mos gjejë zgjidhjen perfekte, edhe nëse ajo ekziston.

Analiza globale

Ky seksion i kushtohet përshkrimit të një qasjeje të ndërmjetme. Zgjidhjet kryesore praktike janë paraqitur në leksionin 17.

Gjatë studimit të opsionit të fiksimit, vumë re se ideja kryesore e tij ishte ndarja e grupeve të entiteteve kovariante dhe polimorfike. Pra, nëse marrim dy udhëzime të formularit


secila prej tyre shërben si shembull i zbatimit të saktë të mekanizmave të rëndësishëm OO: i pari është polimorfizmi, i dyti është ripërcaktimi i tipit. Problemet fillojnë kur kombinohen për të njëjtin ent s. Po kështu:


problemet fillojnë me kombinimin e dy operatorëve të pavarur dhe krejtësisht të pafajshëm.

Thirrjet e gabuara çojnë në shkelje të tipit. Në shembullin e parë, një detyrë polimorfike shton një objekt DJALI drejt e në temë s, çfarë po bën ai g argument i pavlefshëm ndajnë, pasi lidhet me objektin VAJZE. Në shembullin e dytë për entitetin r objekti bashkohet DREJTKËNDËSH, e cila përjashton shtoj_kulm nga komponentët e eksportuar.

Këtu është ideja për një zgjidhje të re: paraprakisht - në mënyrë statike, kur kontrollojmë llojet nga përpiluesi ose mjete të tjera - ne përcaktojmë tipografiçdo entitet, duke përfshirë llojet e objekteve me të cilat entiteti mund të shoqërohet në kohën e ekzekutimit. Pastaj, përsëri në mënyrë statike, sigurohemi që çdo thirrje është e saktë për çdo element të llojit të synuar dhe grupeve të argumenteve.

Në shembujt tanë operatori s:=b tregon se klasa DJALI i përket grupit të llojeve për s(sepse si rezultat i ekzekutimit të instruksionit të krijimit krijoni b i përket grupit të llojeve për b). VAJZE, për shkak të pranisë së udhëzimeve krijoj g, i përket grupit të llojeve për g. Por pastaj sfida ndajnë do të jetë e papranueshme për këtë qëllim s lloji DJALI dhe argumenti g lloji VAJZE. Po kështu DREJTKËNDËSHështë në llojin e caktuar për fq, e cila është për shkak të caktimit polimorfik, megjithatë, thirrja shtoj_kulm Për fq lloji DREJTKËNDËSH do të rezultojë e papranueshme.

Këto vëzhgime na bëjnë të mendojmë për krijimin globale qasje e bazuar në një rregull të ri të shtypjes:

Rregulli i korrektësisë së sistemit

Thirrni x.f(arg)është sistem-korrekt nëse dhe vetëm nëse është i saktë për klasën x, Dhe arg, duke pasur çdo lloj nga grupet e tyre përkatëse të tipit.

Në këtë përkufizim, një thirrje konsiderohet e saktë e klasës nëse nuk shkel rregullin e thirrjes së komponentëve, i cili thotë: nëse C ekziston një klasë bazë e tipit x, komponent f duhet të eksportohet C, dhe lloji arg duhet të jetë në përputhje me llojin e parametrit formal f. (Mos harroni: për thjeshtësi, ne supozojmë se çdo rutinë ka vetëm një parametër, por nuk është e vështirë të zgjerohet rregulli në një numër arbitrar argumentesh.)

Korrektësia e sistemit të një thirrjeje reduktohet në korrektësinë e klasës, me përjashtim që kontrollohet jo për elementë individualë, por për çdo çift nga grupet e grupeve. Këtu janë rregullat themelore për krijimin e një grupi llojesh për çdo entitet:

1 Për çdo entitet, grupi fillestar i llojeve është bosh.

2 Duke hasur në një udhëzim tjetër të formularit krijo (SOME_TYPE) a, shtoni SOME_TYPE në një grup llojesh për a. (Për thjeshtësi, ne do të supozojmë se çdo udhëzim krijoni një do të zëvendësohet me udhëzime krijoj(ATYPE)a, Ku ATYPE- lloji i entitetit a.)

3 Duke hasur në një detyrë tjetër të formularit a:= b, shtoni në grupin e llojeve për a b.

4 Nëse a ekziston një parametër formal i nënprogramit, atëherë, duke u përballur me thirrjen tjetër me një parametër aktual b, shtoni në grupin e llojeve për a të gjithë elementët e tipit të caktuar për b.

5 Do të përsërisim hapat (3) dhe (4) derisa grupet e llojeve të ndalojnë së ndryshuari.

Ky formulim nuk merr parasysh mekanizmin e universalitetit, por rregulli mund të zgjerohet sipas nevojës pa asnjë problem. Hapi (5) është i nevojshëm për shkak të mundësisë së caktimit dhe transferimit të zinxhirëve (nga b te a, nga c te b etj). Nuk është e vështirë të kuptohet se pas një numri të caktuar hapash ky proces do të ndalet.

Siç mund ta keni vënë re, rregulli nuk merr parasysh sekuencën e udhëzimeve. Kur


krijoj (TYPE1)t; s:=t; krijoj (TYPE2)t

në një grup llojesh për s do të hyjë si LLOJI 1, kështu që LLOJI 2, Edhe pse s, duke pasur parasysh sekuencën e udhëzimeve, është në gjendje të pranojë vlera vetëm të llojit të parë. Marrja parasysh e vendndodhjes së udhëzimeve do të kërkojë që përpiluesi të analizojë thellësisht rrjedhën e udhëzimeve, gjë që do të çojë në një rritje të tepruar të nivelit të kompleksitetit të algoritmit. Në vend të kësaj, zbatohen rregulla më pesimiste: sekuenca e veprimeve është:


do të deklarohen si sistem i pasaktë, pavarësisht se sekuenca e ekzekutimit të tyre nuk çon në shkelje të llojit.

Një analizë globale e sistemit u prezantua (në mënyrë më të detajuar) në kapitullin 22 të monografisë. Në të njëjtën kohë, u zgjidh si problemi i kovariancës ashtu edhe problemi i kufizimeve të eksportit gjatë trashëgimisë. Megjithatë, kjo qasje ka një të metë praktike të bezdisshme, domethënë: supozon kontroll sistemin në tërësi, dhe jo çdo klasë veç e veç. Vrasësi rezulton të jetë rregulli (4), i cili, kur thërret një nënprogram të bibliotekës, do të marrë parasysh të gjitha thirrjet e tij të mundshme në klasat e tjera.

Edhe pse më pas u propozuan algoritme për të punuar me klasa individuale, vlera e tyre praktike nuk mund të përcaktohet. Kjo do të thoshte se në një mjedis programimi që mbështet kompilimin në rritje, i gjithë sistemi do të duhej të testohej. Këshillohet që kontrolli të futet si një element i përpunimit (të shpejtë) lokal të ndryshimeve të bëra nga përdoruesi në disa klasa. Megjithëse dihen shembuj të përdorimit të qasjes globale, për shembull, programuesit C përdorin mjetin garzë për të gjetur mospërputhje në sistem që nuk zbulohen nga përpiluesi - e gjithë kjo nuk duket shumë tërheqëse.

Si rezultat, me sa di unë, kontrolli i korrektësisë së sistemit mbeti i pazbatuar nga askush. (Një arsye tjetër për këtë rezultat mund të ketë qenë kompleksiteti i vetë rregullave të verifikimit.)

Korrektësia e klasës përfshin kontrollimin e kufizuar në klasë dhe për këtë arsye është e mundur me përpilim në rritje. Korrektësia e sistemit presupozon një kontroll global të të gjithë sistemit, i cili bie ndesh me kompilimin në rritje.

Megjithatë, pavarësisht nga emri i tij, është në të vërtetë e mundur të kontrollohet korrektësia e sistemit duke përdorur vetëm kontrollin në rritje të klasës (gjatë ekzekutimit të një përpiluesi të rregullt). Ky do të jetë kontributi përfundimtar për zgjidhjen e problemit.

Kujdes nga thirrjet polimorfike!

Rregulli i korrektësisë së sistemit është pesimist: për hir të thjeshtësisë, ai refuzon kombinime plotësisht të sigurta të udhëzimeve. Në mënyrë paradoksale, ne do të ndërtojmë zgjidhjen e fundit bazuar në rregull edhe më pesimist. Natyrisht, kjo do të ngrejë pyetjen se sa real do të jetë rezultati ynë.

Kthehu në Jaltë

Thelbi i zgjidhjes Catcall, - do të shpjegojmë kuptimin e këtij koncepti më vonë, - në rikthim në frymën e marrëveshjeve të Jaltës, duke e ndarë botën në polimorfike dhe bashkëvariante (dhe shoqëruesi i kovariancës është fshehja e pasardhësve), por pa nevojën për të zotëruar. urtësi e pafund.

Si më parë, le ta ngushtojmë çështjen e kovariancës në dy operacione. Në shembullin tonë kryesor, kjo është një detyrë polimorfike: s:=b, dhe thirrni nënprogramin e bashkëvariantit: s.share(g). Duke analizuar se kush është fajtori i vërtetë i shkeljeve, do të përjashtojmë argumentin g nga mesi i të dyshuarve. Çdo argument i llojit SKIER ose që rrjedh prej tij, nuk është i përshtatshëm për ne për shkak të polimorfizmit s dhe kovarianca ndajnë. Prandaj, nëse e përshkruajmë në mënyrë statike entitetin tjera Si SKIER dhe i bashkëngjitni në mënyrë dinamike objektit SKIER, pastaj thirrja s.share (tjetër) në mënyrë statike do të japë përshtypjen e një opsioni ideal, por do të çojë në shkelje të tipit nëse caktohet në mënyrë polimorfike s kuptimi b.

Problemi themelor është se ne po përpiqemi të përdorim s në dy mënyra të papajtueshme: si një entitet polimorfik dhe si objektiv i një thirrjeje nënprograme bashkëvariante. (Në shembullin tonë tjetër, problemi është përdorimi fq si një entitet polimorfik dhe si objektiv i një thirrjeje nënprograme fëmijësh që fsheh komponentin shtoj_kulm.)

Zgjidhja e Catcall, ashtu si Konsolidimi, është radikale: ndalon përdorimin e një entiteti si polimorfik ashtu edhe si bashkëvariant. Ashtu si analizimi global, ai përcakton në mënyrë statike se cilat entitete mund të jenë polimorfike, por nuk përpiqet të jetë shumë i zgjuar duke gjetur grupe të llojeve të mundshme për entitetet. Në vend të kësaj, çdo entitet polimorfik perceptohet si mjaft i dyshimtë dhe i ndalohet të hyjë në një aleancë me një rreth personash të respektuar, duke përfshirë bashkëvariancën dhe fshehjen nga një pasardhës.

Një rregull dhe disa përkufizime

Rregulli i llojit për zgjidhjen e Catcall ka një formulim të thjeshtë:

Shkruani rregullin për Catcall

Thirrjet polimorfike janë të pasakta.

Ai bazohet në përkufizime po aq të thjeshta. Para së gjithash, një entitet polimorfik:

Përkufizimi: entitet polimorfik

Thelbi x Një lloj referimi (i pazgjeruar) është polimorfik nëse ka një nga vetitë e mëposhtme:

1 Ndodh në detyrë x:=y, ku është subjekti y ka një lloj tjetër ose është polimorfik nga rekursioni.

2 Gjendet në udhëzimet e krijimit krijo (OTHER_TYPE) x, Ku OTHER_TYPE nuk është lloji i specifikuar në deklaratë x.

3 Është një argument formal për një nënprogram.

4 Është një funksion i jashtëm.

Qëllimi i këtij përkufizimi është t'i japë statusin e polimorfikut ("potencialisht polimorfik") për çdo entitet që mund t'i bashkëngjitet objekteve të llojeve të ndryshme gjatë ekzekutimit të programit. Ky përkufizim vlen vetëm për llojet e referencës, pasi entitetet e zgjeruara nga natyra nuk mund të jenë polimorfike.

Në shembujt tanë, skiatori s dhe shumëkëndëshi fq- polimorfike sipas rregullit (1). Të parës prej tyre i caktohet një objekt DJALI b, e dyta - objekti DREJTKËNDËSH.

Nëse jeni njohur me formulimin e konceptit të një grupi tipi, do të vini re se sa më pesimist duket përkufizimi i një entiteti polimorfik dhe sa më i lehtë është të kontrollohet. Pa u përpjekur të gjejmë të gjitha llojet e mundshme dinamike të një entiteti, mjaftohemi me pyetjen e përgjithshme: a mund të jetë një ent i caktuar polimorfik apo jo? Rregulli më befasues është (3), sipas të cilit polimorfike numëron çdo parametër formal(përveç nëse lloji i tij është i zgjeruar, siç është rasti me numrat e plotë, etj.). Ne as që mundohemi të analizojmë sfidat. Nëse një nënprogram ka një argument, atëherë ai është tërësisht në dispozicion të klientit, që do të thotë se lloji i specifikuar në deklaratë nuk mund të mbështetet. Ky rregull është i lidhur ngushtë me ripërdorimin - qëllimi i teknologjisë së objektit - ku çdo klasë mund të përfshihet në një bibliotekë dhe të thirret në mënyrë të përsëritur nga klientë të ndryshëm.

Një tipar karakteristik i këtij rregulli është se ai nuk kërkon asnjë kontroll global. Për të identifikuar polimorfizmin e një entiteti, mjafton të shikoni tekstin e vetë klasës. Nëse për të gjitha pyetjet (atributet ose funksionet) ruhet informacioni në lidhje me statusin e tyre polimorfik, atëherë nuk duhet as të studioni tekstet e paraardhësve. Ndryshe nga gjetja e grupeve të llojeve, ju mund të zbuloni entitete polimorfike duke kontrolluar klasë pas klasë në një proces përpilimi në rritje.

Thirrjet, si entitetet, mund të jenë polimorfike:

Përkufizimi: thirrje polimorfike

Një thirrje është polimorfike nëse objektivi i saj është polimorfik.

Të dy thirrjet në shembujt tanë janë polimorfikë: s.share(g) për shkak të polimorfizmit s, p.add_vertex(...) për shkak të polimorfizmit fq. Sipas përkufizimit, vetëm thirrjet e kualifikuara mund të jenë polimorfike. (Duke bërë një telefonatë të pakualifikuar f (...) lloj i kualifikuar Aktuale.f (...), ne nuk e ndryshojmë thelbin e çështjes, sepse Aktuale, të cilit nuk mund t'i caktohet asgjë, nuk është një objekt polimorfik.)

Më pas na duhet koncepti i Catcall, i cili bazohet në konceptin e CAT. (CAT është një akronim për ndryshimin e disponueshmërisë ose llojit). Një nënprogram është një nënprogram CAT nëse një ripërcaktim i tij nga një pasardhës rezulton në një nga dy llojet e ndryshimeve që, siç e kemi parë, janë potencialisht të rrezikshme: ndryshimi i llojit të argumentit (në mënyrë bashkëvariante) ose fshehja e një komponenti të eksportuar më parë.

Përkufizimi: rutinat CAT

Një rutinë quhet rutinë CAT nëse ndonjë ndryshim i saj ndryshon statusin e eksportit ose llojin e ndonjë prej argumenteve të saj.

Kjo veçori i nënshtrohet përsëri testimit në rritje: çdo ndryshim i llojit të argumentit ose statusit të eksportit e bën procedurën ose funksionin një nënprogram CAT. Kjo çon në konceptin e një Catcall: një thirrje për një rutinë CAT që mund të dështojë.

Përkufizimi: Catcall

Një thirrje quhet Catcall nëse ndonjë ndryshim i nënprogramit do ta bënte atë të dështonte për shkak të një ndryshimi në statusin e eksportit ose llojin e argumentit.

Klasifikimi që krijuam na lejon të identifikojmë grupe të veçanta thirrjesh: polimorfike dhe catcalls. Thirrjet polimorfike i shtojnë fuqinë shprehëse qasjes së bazuar në objekte, ndërsa thirrjet matëse ju lejojnë të anashkaloni llojet dhe të kufizoni eksportet. Duke përdorur terminologjinë e prezantuar më herët në këtë leksion, thirrjet polimorfike shtrihen dobia, macet - përdorshmërisë.

Sfidat ndajnë Dhe shtoj_kulm, të diskutuara në shembujt tanë, janë thirrjet e maceve. I pari kryen një ripërkufizim kovariant të argumentit të tij. E dyta eksportohet nga klasa DREJTKËNDËSH, por të fshehura nga klasa POLIGONI. Të dyja thirrjet janë gjithashtu polimorfike, duke i bërë ato shembuj të shkëlqyeshëm të thirrjeve polimorfike. Ato janë të gabuara sipas rregullit të tipit Catcall.

Gradë

Përpara se të mbledhim gjithçka që kemi mësuar rreth kovariancës dhe fshehjes së fëmijëve, le të kujtojmë sërish se shkeljet e korrektësisë në sisteme janë vërtet të rralla. Karakteristikat më të rëndësishme të shtypjes statike të orientuar nga objekti u përmblodhën në fillim të leksionit. Ky grup mbresëlënës i mekanizmave të manipulimit të tipit, i shoqëruar me kontrollin e korrektësisë së klasës, hap rrugën drejt një metode të sigurt dhe fleksibël të ndërtimit të softuerit.

Ne kemi parë tre zgjidhje për problemin e kovariancës, dy prej të cilave trajtuan gjithashtu çështjet e kufizimeve të eksportit. Cili është i saktë?

Nuk ka përgjigje përfundimtare për këtë pyetje. Implikimet e ndërveprimit tinëzar të shtypjes OO dhe polimorfizmit nuk kuptohen aq mirë sa çështjet e paraqitura në leksionet e mëparshme. Vitet e fundit janë shfaqur botime të shumta për këtë temë, referenca për të cilat jepen në bibliografinë në fund të leksionit. Për më tepër, shpresoj që në këtë leksion të kem mundur të paraqes elementet e zgjidhjes përfundimtare, ose të paktën t'i afrohem asaj.

Një analizë globale duket jopraktike për shkak të një auditimi të plotë të të gjithë sistemit. Megjithatë, na ndihmoi ta kuptonim më mirë problemin.

Zgjidhja e pinning është jashtëzakonisht tërheqëse. Është e thjeshtë, intuitive dhe e lehtë për t'u zbatuar. Aq më tepër duhet të na vjen keq për pamundësinë e mbështetjes në të një sërë kërkesash kryesore të metodës OO, të pasqyruara në parimin e hapur-mbyllur. Nëse do të kishim vërtet një intuitë të shkëlqyer, atëherë fiksimi do të ishte një zgjidhje e shkëlqyeshme, por cili zhvillues do të guxonte ta thoshte këtë, ose, për më tepër, të pranonte që autorët e klasave të bibliotekës të trashëguara në projektin e tij kishin një intuitë të tillë?

Nëse jemi të detyruar të braktisim konsolidimin, atëherë zgjidhja Catcall duket të jetë më e përshtatshme, mjaft e lehtë për t'u shpjeguar dhe e zbatueshme në praktikë. Pesimizmi i tij nuk duhet të përjashtojë kombinimet e dobishme të operatorëve. Në rastin kur një catcall polimorfik gjenerohet nga një operator "legjitim", është gjithmonë e mundur të pranohet në mënyrë të sigurt duke futur një përpjekje për caktimin. Kështu, një numër kontrollesh mund të transferohen në kohën e ekzekutimit të programit. Megjithatë, numri i rasteve të tilla duhet të jetë jashtëzakonisht i vogël.

Për sqarim, duhet të vërej se në momentin e shkrimit zgjidhja Catcall nuk është zbatuar. Derisa përpiluesi të përshtatet për të kontrolluar rregullin e tipit Catcall dhe të zbatohet me sukses në sistemet përfaqësuese të mëdha dhe të vogla, është shumë herët të thuhet se fjala e fundit është thënë për problemin e harmonizimit të shtypjes statike me polimorfizmin e kombinuar me kovariancën dhe fshehjen e fëmijëve. .

Pajtueshmëri e plotë

Për të përfunduar diskutimin tonë të kovariancës, është e dobishme të kuptojmë se si një metodë e përgjithshme mund të zbatohet për një problem mjaft të përgjithshëm. Metoda u shfaq si rezultat i teorisë Catcall, por mund të përdoret brenda kornizës së versionit bazë të gjuhës pa futur rregulla të reja.

Le të jenë dy lista të koordinuara, ku e para specifikon skiatorët, dhe e dyta specifikon një shok dhomë për një skiator nga lista e parë. Ne duam të ndjekim procedurën e duhur të vendosjes ndajnë, vetëm nëse lejohet nga rregullat e përshkrimit të tipit, të cilat lejojnë vajzat të vendosen me vajzat, vajzat fituese me vajzat fituese, e kështu me radhë. Problemet e këtij lloji janë të zakonshme.

Mund të ketë një zgjidhje të thjeshtë bazuar në diskutimin e mëparshëm dhe përpjekjen për detyrë. Merrni parasysh funksionin universal të pajisura(mirato):


i pajisur (tjetër: E PËRGJITHSHME): si të tjerat është
-- Objekti aktual (Aktual), nëse lloji i tij përputhet me llojin e objektit,
-- ngjitur me një tjetër, përndryshe i pavlefshëm.
nëse tjetër /= Void dhe pastaj përputhet_me (tjetër) atëherë

Funksioni të pajisura kthen objektin aktual, por i njohur si entiteti i tipit të bashkangjitur argumentit. Nëse lloji i objektit aktual nuk përputhet me llojin e objektit të bashkangjitur argumentit, atëherë E pavlefshme. Vini re rolin e përpjekjes për detyrë. Funksioni përdor një komponent përputhet me nga klasa E PËRGJITHSHME, i cili përcakton përputhshmërinë e tipit të një çifti objektesh.

Zëvendësimi përputhet me në një komponent tjetër E PËRGJITHSHME Me emër lloji i njëjtë na jep një funksion perfekte (pajtueshmërinë e plotë), i cili kthehet E pavlefshme, nëse llojet e të dy objekteve nuk janë identike.

Funksioni të pajisura- na jep një zgjidhje të thjeshtë për problemin e përputhjes së skiatorit pa shkelur rregullat e përshkrimit të tipit. Pra, në kodin e klasës SKIER ne mund të prezantojmë një procedurë të re dhe ta përdorim në vend të saj ndajnë, (kjo e fundit mund të bëhet si procedurë e fshehtë).


-- Zgjidhni, nëse është e mundur, një tjetër si fqinj sipas numrit.
-- gjinia e konstatuar - gjinia e caktuar
gjinia_pohuar_tjetër: si Aktuale
gjinia_konstatuar_tjetër:= tjetër .përshtatur (aktuale)
nëse gjinia_pohohet_tjetër /= E pavlefshme atëherë
share (gjinia_e_pohuar_tjetër)
"Përfundim: bashkëvendosja me të tjerët nuk është e mundur"

Për tjera lloj arbitrar SKIER(jo vetem si Rryma) përcaktoni versionin gjinia_pohuar_tjetër, që i është caktuar një lloj Aktuale. Funksioni do të na ndihmojë të garantojmë identitetin e llojeve perfekte.

Nëse ka dy lista paralele të skiatorëve që përfaqësojnë akomodimin e planifikuar:


banuesi1, banuesi2: LIST

Mund të organizoni një lak duke thirrur në çdo hap:


occupant1.item.safe_share (occupant2.item)

përputhen elementet e listave nëse dhe vetëm nëse llojet e tyre janë plotësisht të pajtueshme.

Konceptet kryesore

[x]. Shkrimi statik është çelësi i besueshmërisë, lexueshmërisë dhe efikasitetit.

[x]. Për të qenë realist, shtypja statike kërkon një kombinim mekanizmash: pohime, trashëgimi e shumëfishtë, caktim i tentuar, shkathtësi e kufizuar dhe e pakufishme, deklarata të ankoruara. Sistemi i tipit nuk duhet të lejojë kurthe (lloji hedhje).

[x]. Rregullat e përgjithshme për rideklarimin duhet të lejojnë ripërkufizimin bashkëvariant. Llojet e rezultateve dhe argumenteve kur anashkalohen duhet të jenë të pajtueshme me ato origjinale.

[x]. Kovarianca, si dhe mundësia e fshehjes së një pasardhësi të një komponenti të eksportuar nga një paraardhës, e kombinuar me polimorfizmin, krijon problemin e rrallë por shumë serioz të shkeljes së tipit.

[x]. Këto shkelje mund të shmangen duke përdorur: analizën globale (e cila është jopraktike), duke kufizuar kovariancën në tipe fikse (që është në kundërshtim me parimin Open-Closed), një zgjidhje Catcall që parandalon një objektiv polimorfik të thërrasë një nënprogram me kovariancë ose të fshihet nga një fëmijë.

Shënime bibliografike

Një numër materialesh nga ky leksion u prezantuan në raporte në forumet OOPSLA 95 dhe TOOLS PACIFIC 95, dhe u publikuan gjithashtu në. Një numër materialesh rishikimi janë huazuar nga artikulli.

Koncepti i konkluzionit automatik të tipit u prezantua në , i cili përshkruan algoritmin e konkluzionit të tipit për gjuhën funksionale ML. Marrëdhënia midis polimorfizmit dhe kontrollit të tipit u hulumtua në.

Teknikat për rritjen e efikasitetit të kodit të gjuhëve të shtypura në mënyrë dinamike në kontekstin e gjuhës së vetvetes mund të gjenden në.

Një artikull teorik mbi llojet në gjuhët e programimit që patën një ndikim të madh te specialistët u shkrua nga Luca Cardelli dhe Peter Wegner. Kjo punë, e ndërtuar mbi bazën e llogaritjes lambda (shih), shërbeu si bazë për shumë kërkime të mëtejshme. Atij i parapriu një artikull tjetër themelor nga Cardelli.

Tutoriali ISE përfshin një hyrje në problemet e përdorimit të polimorfizmit, kovariancës dhe fshehjes së fëmijëve së bashku. Mungesa e analizës së duhur në botimin e parë të këtij libri shkaktoi një sërë diskutimesh kritike (e para prej të cilave ishin komentet e Philippe Elinck në tezën e tij të diplomës "De la Conception-Programmation par Objets", Memoire de licence, Universite Libre de Bruxelles (Belgjikë), 1988), e shprehur në vepra dhe. Punimi i Cook ofron disa shembuj në lidhje me problemin e kovariancës dhe përpjekjet për ta zgjidhur atë. Një zgjidhje e bazuar në parametrat standardë për entitetet bashkëvariante u propozua nga Franz Weber në TOOLS EUROPE 1992. Janë dhënë përkufizime të sakta të koncepteve të korrektësisë së sistemit, si dhe korrektësisë së klasës, dhe aty propozohet një zgjidhje duke përdorur një analizë të plotë të sistemit. Zgjidhja e Catcall u propozua për herë të parë në; Shiko gjithashtu .

Zgjidhja e fiksimit u prezantua në fjalimin tim në seminarin TOOLS EUROPE 1994. Megjithatë, në atë kohë nuk e pashë nevojën për spirancë- reklamat dhe kufizimet përkatëse të përputhshmërisë. Paul Dubois dhe Amiram Yehudai nxituan të theksojnë se në këto kushte problemi i kovariancës mbetet. Ata, si dhe Reinhardt Budde, Karl-Heinz Sylla, Kim Walden dhe James McKim, bënë shumë komente që ishin të një rëndësie themelore në punën që çoi në shkrimin e kësaj leksioni.

Një sasi e madhe literaturë i kushtohet çështjeve të kovariancës. Në dhe do të gjeni një bibliografi të gjerë dhe një pasqyrë të aspekteve matematikore të problemit. Për një listë të lidhjeve me materialet online mbi teorinë e tipit në OOP dhe faqet e internetit të autorëve të tyre, shihni faqen e Laurent Dami. Konceptet e kovariancës dhe kontravariancës janë huazuar nga teoria e kategorisë. Paraqitjen e tyre në kontekstin e shtypjes së softuerëve ia detyrojmë Luca Cardelli, i cili filloi t'i përdorte në fjalimet e tij në fillim të viteve '80, por nuk i përdori ato në shtyp deri në fund të viteve '80.

Teknikat e bazuara në variabla standarde janë përshkruar në , , .

Kontravarianca u zbatua në gjuhën Sather. Shpjegimet janë dhënë në.

Typing - caktimi i një lloji për entitetet e informacionit.

Llojet më të zakonshme primitive të të dhënave janë:

  • Numerike
  • simbolike
  • Logjike

Funksionet kryesore të sistemit të tipit të të dhënave:

  • Siguria
    Çdo operacion kontrollohet për t'u siguruar që ai merr argumente saktësisht të llojeve për të cilat është menduar;
  • Optimizimi
    Bazuar në llojin, zgjidhet një metodë e ruajtjes efikase dhe algoritme për përpunimin e saj;
  • Dokumentacioni
    Theksohen synimet e programuesit;
  • Abstraksioni
    Përdorimi i llojeve të të dhënave të nivelit të lartë i lejon programuesit të mendojë për vlerat si entitete të nivelit të lartë dhe jo si një koleksion bitesh.

Klasifikimi

Ka shumë klasifikime të shtypjes së gjuhëve të programimit, por ka vetëm 3 kryesore:

Shtypje statike/dinamike

Statike - caktimi dhe verifikimi i konsistencës së tipit kryhet në fazën e përpilimit. Llojet e të dhënave shoqërohen me variabla, jo me vlera specifike. Shtypja statike ju lejon të gjeni gabimet e shtypjes të bëra në degët e logjikës së programit që përdoren rrallë në fazën e përpilimit.

Shtypja dinamike është e kundërta e shtypjes statike. Në shtypjen dinamike, të gjitha llojet zbulohen gjatë ekzekutimit të programit.

Shtypja dinamike ju lejon të krijoni softuer më fleksibël, edhe pse me kosto më të madhe të gjasave për gabime në shkrim. Testimi i njësisë është veçanërisht i rëndësishëm kur zhvillohet softueri në gjuhë programimi të shtypura dinamike, pasi është mënyra e vetme për të gjetur gabime të shtypjes në degët e logjikës së programit që përdoren rrallë.

Shtypja dinamike

Var LuckyNumber = 777; var Emri i faqes = "Tyapk"; // supozojmë një numër, shkruajmë vargun var gabimNumber = "999";

Shtypja statike

Le LuckyNumber: numri = 777; le Emri i faqes: string = "Tyapk"; // do të shkaktojë një gabim let gabimNumber: numër = "999";

  • Statike: Java, C#, TypeScript.
  • Dinamik: Python, Ruby, JavaScript.

Shkrimi i qartë/i nënkuptuar.

Gjuhët e shtypura në mënyrë eksplicite ndryshojnë në atë që lloji i variablave/funksioneve/argumenteve të tyre të reja duhet të specifikohet në mënyrë eksplicite. Prandaj, gjuhët me shtypje të nënkuptuar e zhvendosin këtë detyrë te përpiluesi/interpretuesi. Shtypja e qartë është e kundërta e shtypjes së nënkuptuar.

Shtypja e qartë kërkon një deklaratë të qartë të llojit për çdo variabël të përdorur. Ky lloj shtypjeje është një rast i veçantë i shtypjes statike, sepse lloji i secilës variabël përcaktohet në kohën e kompilimit.

Shkrimi i nënkuptuar

Le të jetë stringVar = "777" + 99; // merrni "77799"

Shkrimi i qartë (gjuhë fiktive e ngjashme me JS)

Le të wrongStringVar = "777" + 99; // do të shkaktojë një gabim let stringVar = "777" + String(99); // merrni "77799"

Shkrimi i fortë/jo i fortë

Quhet edhe shkrimi i fortë/i dobët. Me shtypjen e rreptë, llojet caktohen "një herë e përgjithmonë"; me shtypje jo të rreptë, ato mund të ndryshojnë gjatë ekzekutimit të programit.

Gjuhët e shtypura fort ndalojnë ndryshimet në llojin e të dhënave të një variabli dhe lejojnë vetëm konvertime të qarta të tipit të të dhënave. Shtypja e fortë dallohet nga fakti se gjuha nuk lejon përzierjen e llojeve të ndryshme në shprehje dhe nuk kryen konvertime automatike të nënkuptuara, për shembull, nuk mund të zbresësh një numër nga një varg. Gjuhët e shtypura dobët kryejnë shumë konvertime të nënkuptuara automatikisht, edhe pse mund të ndodhë humbja e saktësisë ose konvertimi është i paqartë.

Shkrimi i fortë (gjuhë fiktive e ngjashme me JS)

Le të gabuarNumri = 777; gabimNumër = gabimNumër + "99"; // marrim një gabim që vargu i shtohet variablit numerik wrongNumber le trueNumber = 777 + Number("99"); // merrni 876

Shkrimi jo i rreptë (siç është në js)

Le të gabuarNumri = 777; gabimNumër = gabimNumër + "99"; // mori vargun "77799"

  • E rreptë: Java, Python, Haskell, Lisp.
  • Jo strikte: C, JavaScript, Visual Basic, PHP.

Ky artikull përmban minimumin e nevojshëm të atyre gjërave që thjesht duhet të dini rreth shtypjes në mënyrë që të mos e quani të keqe shtypjen dinamike, Lisp një gjuhë pa tip dhe C një gjuhë të shtypur fort.

Versioni i plotë përmban një përshkrim të detajuar të të gjitha llojeve të shtypjes, të kalitur me shembuj kodesh, lidhje me gjuhët e njohura të programimit dhe fotografi ilustruese.

Unë rekomandoj të lexoni fillimisht versionin e shkurtër të artikullit dhe më pas versionin e plotë nëse dëshironi.

Version i shkurtër

Në bazë të shtypjes, gjuhët e programimit zakonisht ndahen në dy kampe të mëdha - të shtypura dhe të pashtypura (pa tip). E para përfshin, për shembull, C, Python, Scala, PHP dhe Lua, dhe e dyta përfshin gjuhën e asamblesë, Forth dhe Brainfuck.

Meqenëse "shtypja pa tip" në thelb është aq e thjeshtë sa një prizë, ajo nuk ndahet më tej në lloje të tjera. Por gjuhët e shtypura ndahen në disa kategori më të mbivendosura:

  • Shtypje statike/dinamike. Static përcaktohet nga fakti se llojet përfundimtare të variablave dhe funksioneve vendosen në kohën e kompilimit. Ato. përpiluesi tashmë është 100% i sigurt se cili lloj është ku. Në shtypjen dinamike, të gjitha llojet zbulohen gjatë ekzekutimit të programit.

    Shembuj:
    Statike: C, Java, C#;
    Dinamik: Python, JavaScript, Ruby.

  • Shkrimi i fortë/i dobët (gjithashtu nganjëherë quhet i fortë/i dobët). Shtypja e fortë dallohet nga fakti se gjuha nuk lejon përzierjen e llojeve të ndryshme në shprehje dhe nuk kryen konvertime automatike të nënkuptuara, për shembull, nuk mund të zbritësh një grup nga një varg. Gjuhët e shtypura dobët kryejnë shumë konvertime të nënkuptuara automatikisht, edhe pse mund të ndodhë humbja e saktësisë ose konvertimi është i paqartë.

    Shembuj:
    E fortë: Java, Python, Haskell, Lisp;
    E dobët: C, JavaScript, Visual Basic, PHP.

  • Shkrimi i qartë/i nënkuptuar. Gjuhët e shtypura në mënyrë eksplicite ndryshojnë në atë që lloji i variablave/funksioneve/argumenteve të tyre të reja duhet të specifikohet në mënyrë eksplicite. Prandaj, gjuhët me shtypje të nënkuptuar e zhvendosin këtë detyrë te përpiluesi/interpretuesi.

    Shembuj:
    E qartë: C++, D, C#
    Implicite: PHP, Lua, JavaScript

Duhet gjithashtu të theksohet se të gjitha këto kategori mbivendosen, për shembull, gjuha C ka shtypje statike të dobët eksplicite, dhe gjuha Python ka shtypje dinamike të nënkuptuar të fortë.

Sidoqoftë, nuk ka gjuhë me shtypje statike dhe dinamike në të njëjtën kohë. Megjithëse, duke parë përpara, unë do të them që jam shtrirë këtu - ato ekzistojnë vërtet, por më shumë për këtë më vonë.

Versioni i detajuar

Nëse versioni i shkurtër nuk është i mjaftueshëm për ju, është mirë. Nuk është më kot që kam shkruar një të detajuar? Gjëja kryesore është se ishte thjesht e pamundur të përshtateshin të gjitha informacionet e dobishme dhe interesante në një version të shkurtër, dhe një i detajuar ndoshta do të ishte shumë i gjatë që të gjithë të lexonin pa u lodhur.

Shtypje pa tip

Në gjuhët e programimit pa tip, të gjitha entitetet konsiderohen thjesht sekuenca të biteve me gjatësi të ndryshme.

Shtypja pa tipare është zakonisht e natyrshme në gjuhët e nivelit të ulët (gjuhë asambleje, Forth) dhe ezoterike (Brainfuck, HQ9, Piet). Megjithatë, së bashku me disavantazhet e tij, ajo ka edhe disa avantazhe.

Përparësitë
  • Ju lejon të shkruani në një nivel jashtëzakonisht të ulët dhe përpiluesi/interpretuesi nuk do të ndërhyjë në asnjë lloj kontrolli. Ju jeni të lirë të kryeni çdo operacion në çdo lloj të dhënash.
  • Kodi që rezulton është zakonisht më efikas.
  • Transparenca e udhëzimeve. Nëse e dini gjuhën, zakonisht nuk ka dyshim se çfarë është ky apo ai kod.
Të metat
  • Kompleksiteti. Shpesh ekziston nevoja për të përfaqësuar vlera komplekse si listat, vargjet ose strukturat. Kjo mund të shkaktojë bezdi.
  • Mungesa e kontrolleve. Çdo veprim i pakuptimtë, si zbritja e një treguesi në një grup nga një simbol, do të konsiderohet krejtësisht normale, e cila është e mbushur me gabime delikate.
  • Niveli i ulët i abstraksionit. Puna me çdo lloj të dhënash komplekse nuk ndryshon nga puna me numra, gjë që sigurisht do të krijojë shumë vështirësi.
Shtypje e fortë pa tip?

Po, kjo ekziston. Për shembull, në gjuhën e asamblesë (për arkitekturën x86/x86-64, nuk i njoh të tjerët) nuk mund të montoni një program nëse përpiqeni të ngarkoni të dhëna nga regjistri rax (64 bit) në regjistrin cx (16 bit) .

mov cx, eax ; gabim në kohën e montimit

Pra, rezulton se asembleri ka ende shtypje? Besoj se këto kontrolle nuk mjaftojnë. Dhe mendimi juaj, natyrisht, varet vetëm nga ju.

Shtypje statike dhe dinamike

Gjëja kryesore që dallon shtypjen statike nga shtypja dinamike është se i gjithë kontrolli i tipit kryhet në kohën e kompilimit, jo në kohën e ekzekutimit.

Disa njerëz mund të mendojnë se shtypja statike është shumë kufizuese (në fakt, është, por kjo është eliminuar prej kohësh me ndihmën e disa teknikave). Disa njerëz thonë se gjuhët e shtypura në mënyrë dinamike po luajnë me zjarrin, por cilat veçori i bëjnë ato të dallohen? A kanë vërtet mundësi të ekzistojnë të dyja speciet? Nëse jo, atëherë pse ka kaq shumë gjuhë që shtypen në mënyrë statike dhe dinamike?

Le ta kuptojmë.

Përfitimet e shtypjes statike
  • Kontrollimi i tipit ndodh vetëm një herë - në fazën e përpilimit. Kjo do të thotë që nuk do të na duhet të kuptojmë vazhdimisht nëse po përpiqemi të ndajmë një numër me një varg (dhe ose të hedhim një gabim ose të kryejmë një konvertim).
  • Shpejtësia e ekzekutimit. Nga pika e mëparshme është e qartë se gjuhët e shtypura në mënyrë statike janë pothuajse gjithmonë më të shpejta se ato të shtypura në mënyrë dinamike.
  • Në disa kushte shtesë, ju lejon të zbuloni gabime të mundshme tashmë në fazën e përpilimit.
Përfitimet e shtypjes dinamike
  • Lehtësia e krijimit të koleksioneve universale - grumbuj të gjithçkaje dhe të gjithëve (rrallë lind një nevojë e tillë, por kur lind shtypja dinamike do të ndihmojë).
  • Lehtësia e përshkrimit të algoritmeve të përgjithësuara (për shembull, renditja e grupeve, e cila do të funksionojë jo vetëm në një listë numrash të plotë, por edhe në një listë numrash realë dhe madje edhe në një listë vargjesh).
  • Lehtë për t'u mësuar - Gjuhët e shtypura në mënyrë dinamike janë zakonisht shumë të mira për të filluar me programim.

Programim i përgjithësuar

Mirë, argumenti më i rëndësishëm për shtypjen dinamike është lehtësia e përshkrimit të algoritmeve gjenerike. Le të imagjinojmë një problem - na duhet një funksion për të kërkuar nëpër disa vargje (ose lista) - një grup numrash të plotë, një grup real dhe një grup karakteresh.

Si do ta zgjidhim? Le ta zgjidhim në 3 gjuhë të ndryshme: një me shtypje dinamike dhe dy me shtypje statike.

Unë do të përdor një nga algoritmet më të thjeshta të kërkimit - forcën brutale. Funksioni do të marrë elementin që kërkohet, vetë grupin (ose listën) dhe do të kthejë indeksin e elementit, ose nëse elementi nuk gjendet - (-1).

Zgjidhja dinamike (Python):

Def find(required_element, list): për (indeks, element) në enumerate(list): nëse elementi == elementi_i kërkuar: kthen indeksin (-1)

Siç mund ta shihni, gjithçka është e thjeshtë dhe nuk ka probleme me faktin se lista mund të përmbajë numra, lista ose grupe të tjera. Shume mire. Le të shkojmë më tej - zgjidhni të njëjtin problem në C!

Zgjidhja statike (C):

Int e panënshkruar find_int(int kërkohet_element, grup int, madhësi int e panënshkruar) ( for (int i panënshkruar 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); }

Epo, çdo funksion individualisht është i ngjashëm me versionin Python, por pse ka tre prej tyre? A ka humbur vërtet programimi statik?

Po dhe jo. Ekzistojnë disa teknika programimi, një prej të cilave do të shqyrtojmë tani. Quhet programim gjenerik dhe gjuha C++ e mbështet mjaft mirë. Le të hedhim një vështrim në versionin e ri:

Zgjidhje statike (programim gjenerik, C++):

shabllon i panënshkruar int find(T kërkohet_element, std::vektor grup) ( për (int i panënshkruar i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

Mirë! Nuk duket shumë më i komplikuar se versioni Python dhe nuk kërkon shumë shkrim. Përveç kësaj, ne kemi një zbatim për të gjitha grupet, jo vetëm 3 të nevojshme për të zgjidhur problemin!

Ky version duket se është pikërisht ajo që na nevojitet - ne marrim avantazhet e shtypjes statike dhe disa nga avantazhet e shtypjes dinamike.

Është mirë që kjo është e mundur fare, por mund të jetë edhe më mirë. Së pari, programimi i përgjithësuar mund të jetë më i përshtatshëm dhe më i bukur (për shembull, në gjuhën Haskell). Së dyti, përveç programimit të përgjithësuar, mund të përdorni edhe polimorfizëm (rezultati do të jetë më i keq), mbingarkesa e funksionit (në mënyrë të ngjashme) ose makro.

Statika në dinamikë

Duhet përmendur gjithashtu se shumë gjuhë statike lejojnë shtypjen dinamike, për shembull:

  • C# mbështet pseudo-tipin dinamik.
  • F# mbështet sheqerin sintaksor në formën e operatorit ?, në bazë të të cilit mund të zbatohet imitimi i shtypjes dinamike.
  • Haskell - shtypja dinamike ofrohet nga moduli Data.Dynamic.
  • Delphi - përmes një lloji të veçantë Varianti.

Gjithashtu, disa gjuhë të shtypura në mënyrë dinamike ju lejojnë të përfitoni nga shtypja statike:

  • Common Lisp - deklaratat e tipit.
  • Perl - që nga versioni 5.6, mjaft i kufizuar.

Shkrimi i fortë dhe i dobët

Gjuhët e shtypura fort nuk lejojnë që entitete të llojeve të ndryshme të përzihen në shprehje dhe nuk kryejnë asnjë konvertim automatik. Quhen edhe “gjuhë të shtypura fort”. Termi anglisht për këtë është shtypja e fortë.

Gjuhët e shtypura dobët, përkundrazi, inkurajojnë programuesin të përziejë lloje të ndryshme në një shprehje, dhe vetë përpiluesi do të reduktojë gjithçka në një lloj të vetëm. Ato quhen gjithashtu "gjuhë të shtypura lirshme". Termi anglisht për këtë është shtypja e dobët.

Shkrimi i dobët shpesh ngatërrohet me shtypjen dinamike, gjë që është krejtësisht e gabuar. Një gjuhë e shtypur në mënyrë dinamike mund të shtypet dobët ose me forcë.

Megjithatë, pak njerëz i kushtojnë rëndësi rreptësisë së shtypjes. Shpesh thuhet se nëse një gjuhë shtypet në mënyrë statike, atëherë mund të kapni shumë gabime të mundshme gjatë përpilimit. Ata po ju gënjejnë!

Gjuha gjithashtu duhet të ketë shtypje të fortë. Në të vërtetë, nëse përpiluesi, në vend që të raportojë një gabim, thjesht shton një varg në një numër, ose, edhe më keq, zbret një tjetër nga një grup, çfarë dobie kemi që të gjitha "kontrollet" e llojeve do të jenë në përpilim fazë? Kjo është e drejtë - shtypja e dobët statike është edhe më e keqe se shtypja e fortë dinamike! (Epo, ky është mendimi im)

Pra, a nuk ka fare përparësi shtypja e dobët? Mund të duket kështu, por pavarësisht se jam përkrahës i flaktë i të shtypurit të fortë, duhet të pajtohem që edhe të shtypurit e dobët ka përparësi.

Dëshironi të dini cilat prej tyre?

Përfitimet e shtypjes së fortë
  • Besueshmëria - Do të merrni një përjashtim ose gabim përpilimi në vend të sjelljes së gabuar.
  • Shpejtësia - Në vend të konvertimeve të fshehura, të cilat mund të jenë mjaft të shtrenjta, me shtypje të fortë duhet t'i shkruani ato në mënyrë eksplicite, gjë që e detyron programuesin të paktën të dijë se kjo pjesë e kodit mund të jetë e ngadaltë.
  • Duke kuptuar se si funksionon programi - përsëri, në vend të hedhjes së tipit të nënkuptuar, programuesi shkruan gjithçka vetë, që do të thotë se ai e kupton afërsisht se krahasimi i një vargu dhe një numri nuk ndodh vetvetiu dhe jo me magji.
  • Siguria - kur shkruani transformimet me dorë, e dini saktësisht se çfarë po konvertoni dhe në çfarë. Gjithashtu do të jeni gjithmonë të vetëdijshëm se konvertime të tilla mund të rezultojnë në humbje të saktësisë dhe rezultate të pasakta.
Përfitimet e shtypjes së dobët
  • Lehtësia e përdorimit të shprehjeve të përziera (për shembull, nga numra të plotë dhe numra realë).
  • Abstragimi nga shtypja dhe fokusimi në detyrë.
  • Shkurtësia e hyrjes.

Mirë, e kuptuam, rezulton se edhe shtypja e dobët ka avantazhe! A ka mënyra për të transferuar avantazhet e shtypjes së dobët në shtypje të fortë?

Rezulton se ka edhe dy.

Transmetim i tipit implicit, në situata të paqarta dhe pa humbje të të dhënave

Wow... Një pikë mjaft e gjatë. Më lejoni ta shkurtoj më tej në "konvertim të kufizuar të nënkuptuar" Pra, çfarë do të thotë situata e paqartë dhe humbja e të dhënave?

Një situatë e paqartë është një transformim ose operacion në të cilin thelbi është menjëherë i qartë. Për shembull, shtimi i dy numrave është një situatë e paqartë. Por konvertimi i një numri në një grup nuk është (ndoshta do të krijohet një grup me një element, ndoshta një grup me një gjatësi të tillë, i mbushur me elementë si parazgjedhje, dhe ndoshta numri do të konvertohet në një varg dhe më pas në një grup të personazheve).

Humbja e të dhënave është edhe më e lehtë. Nëse e konvertojmë numrin real 3.5 në një numër të plotë, do të humbasim një pjesë të të dhënave (në fakt, edhe ky operacion është i paqartë - si do të bëhet rrumbullakimi? Lart? Poshtë? Hedhja e pjesës thyesore?).

Konvertimet në situata të paqarta dhe konvertimet me humbje të të dhënave janë shumë, shumë të këqija. Nuk ka asgjë më të keqe se kjo në programim.

Nëse nuk më besoni, studioni gjuhën PL/I ose thjesht kërkoni specifikimet e saj. Ka rregulla për konvertimin midis TË GJITHA llojeve të të dhënave! Ky është vetëm ferr!

Mirë, le të kujtojmë për konvertimin e kufizuar të nënkuptuar. A ka gjuhë të tilla? Po, për shembull në Pascal mund të konvertohet një numër i plotë në një numër real, por jo anasjelltas. Ekzistojnë gjithashtu mekanizma të ngjashëm në C#, Groovy dhe Common Lisp.

Mirë, thashë se ka ende një mënyrë për të marrë disa avantazhe të shtypjes së dobët në një gjuhë të fortë. Dhe po, ekziston dhe quhet polimorfizëm konstruktor.

Unë do ta shpjegoj duke përdorur shembullin e gjuhës së mrekullueshme Haskell.

Konstruktorët polimorfikë u ngritën nga vëzhgimi se konvertimet e sigurta të nënkuptuara nevojiten më shpesh kur përdoren literale numerike.

Për shembull, në shprehjen pi + 1, nuk dëshironi të shkruani pi + 1.0 ose pi + float(1). Unë thjesht dua të shkruaj pi + 1!

Dhe kjo bëhet në Haskell, falë faktit se literali 1 nuk ka një tip konkret. Nuk është as e tërë, as reale, as komplekse. Është vetëm një numër!

Si rezultat, kur shkruajmë një shumë të thjeshtë funksioni x y, duke shumëzuar të gjithë numrat nga x në y (me një rritje prej 1), marrim disa versione njëherësh - shumën për numrat e plotë, shumën për realet, shumën për racionalët, shumën për numrat kompleks. dhe madje shuma për të gjitha ato lloje numerike që ju vetë i keni përcaktuar.

Sigurisht, kjo teknikë kursen vetëm kur përdoren shprehje të përziera me fjalë për fjalë numerike, dhe kjo është vetëm maja e ajsbergut.

Kështu, mund të themi se zgjidhja më e mirë është të balanconi në buzë midis shtypjes së fortë dhe të dobët. Por asnjë gjuhë nuk arrin ende ekuilibrin e përsosur, kështu që unë anoj më shumë drejt gjuhëve të shtypura fort (të tilla si Haskell, Java, C#, Python) sesa atyre të shtypura dobët (si C, JavaScript, Lua, PHP).

Shtypje eksplicite dhe implicite

Një gjuhë e shtypur në mënyrë eksplicite kërkon që programuesi të specifikojë llojet e të gjitha variablave dhe funksioneve që ai deklaron. Termi anglisht për këtë është shtypja e qartë.

Një gjuhë e shtypur në mënyrë implicite, nga ana tjetër, ju inkurajon të harroni llojet dhe t'ia lini detyrën e nxjerrjes së llojeve te përpiluesi ose interpretuesi. Termi anglisht për këtë është shtypja e nënkuptuar.

Në fillim, mund të mendoni se shtypja e nënkuptuar është ekuivalente me dinamike, dhe shtypja eksplicite është e barabartë me statike, por më vonë do të shohim që nuk është kështu.

A ka përparësi për secilin lloj, dhe përsëri, a ka kombinime të tyre dhe a ka gjuhë që mbështesin të dyja metodat?

Përfitimet e Shtypjes eksplicite
  • Duke pasur çdo funksion një nënshkrim (për shembull, int add(int, int)) e bën të lehtë përcaktimin se çfarë bën funksioni.
  • Programuesi menjëherë shkruan se çfarë lloj vlerash mund të ruhen në një variabël të veçantë, duke eliminuar nevojën për ta mbajtur mend atë.
Përfitimet e Shtypjes Implicite
  • Shënimi i shkurtores - def add(x, y) është qartësisht më i shkurtër se int add(int x, int y).
  • Rezistenca ndaj ndryshimit. Për shembull, nëse në një funksion ndryshorja e përkohshme ishte e të njëjtit lloj si argumenti hyrës, atëherë në një gjuhë të shtypur në mënyrë eksplicite, kur ndryshoni llojin e argumentit të hyrjes, do t'ju duhet gjithashtu të ndryshoni llojin e ndryshores së përkohshme.

Mirë, është e qartë se të dyja qasjet kanë të mirat dhe të këqijat (kush priste diçka tjetër?), kështu që le të kërkojmë mënyra për të kombinuar këto dy qasje!

Shkrimi i qartë sipas zgjedhjes

Ka gjuhë me shtypje të nënkuptuar si parazgjedhje dhe aftësinë për të specifikuar llojin e vlerave nëse është e nevojshme. Përkthyesi do të nxjerrë automatikisht llojin e vërtetë të shprehjes. Një nga këto gjuhë është Haskell, më lejoni të jap një shembull të thjeshtë për qartësi:

Pa specifikim eksplicit tip add (x, y) = x + y -- Specifikim eksplicit tip add:: (Integer, Integer) -> Integer add (x, y) = x + y

Shënim: Kam përdorur qëllimisht një funksion të pandërprerë, dhe gjithashtu kam shkruar qëllimisht një nënshkrim privat në vend të shtesës më të përgjithshme:: (Numri a) -> a -> a -> a, sepse Doja të tregoja idenë pa shpjeguar sintaksën Haskell.

Hm. Siç mund ta shohim, është shumë e bukur dhe e shkurtër. Shkrimi i një funksioni merr vetëm 18 karaktere në një rresht, duke përfshirë hapësirat!

Sidoqoftë, konkludimi i tipit automatik është një gjë mjaft komplekse, dhe madje edhe në një gjuhë kaq të lezetshme si Haskell, ndonjëherë dështon. (një shembull është kufizimi i monomorfizmit)

A ka gjuhë me shtypje eksplicite si parazgjedhje dhe shtypje të nënkuptuar nëse është e nevojshme? Kon
me siguri.

Shkrimi i nënkuptuar sipas zgjedhjes

Standardi i ri i gjuhës C++, i quajtur C++11 (i quajtur më parë C++0x), prezantoi fjalën kyçe automatike, e cila i lejon kompajlerit të konkludojë llojin bazuar në kontekst:

Le të krahasojmë: // Duke specifikuar manualisht llojin e panënshkruar int a = 5; i panënshkruar int b = a + 3; // Dalja automatike e tipit të panënshkruar int a = 5; auto b = a + 3;

Jo keq. Por regjistrimi nuk ka rënë shumë. Le të shohim një shembull me përsëritës (nëse nuk e kuptoni, mos kini frikë, gjëja kryesore që duhet të theksohet është se regjistrimi zvogëlohet shumë falë daljes automatike):

// Përcaktimi manual i llojit std::vector vec = Vektor i rastësishëm(30); për (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // Lloji automatik i konkluzionit auto vec = randomVector (tridhjetë); për (auto it = vec.cbegin (); ...) ( ... )

Uau! Kjo është shkurtesa. Mirë, por a është e mundur të bëhet diçka si Haskell, ku lloji i kthimit varet nga llojet e argumenteve?

Dhe përsëri përgjigja është po, falë fjalës kyçe decltype në kombinim me auto:

// Lloji manual int divide(int x, int y) ( ... ) // Përfundimi automatik i tipit ndarje automatike (int x, int y) -> decltype(x / y) ( ... )

Kjo formë shënimi mund të mos duket shumë e mirë, por kur kombinohet me programim gjenerik (shabllone/gjenerikë), shtypja e nënkuptuar ose përfundimi automatik i tipit bën mrekulli.

Disa gjuhë programimi sipas këtij klasifikimi

Unë do të jap një listë të vogël të gjuhëve të njohura dhe do të shkruaj se si ato ndahen në secilën kategori të "shtypjes".

JavaScript - Ruby dinamike / e dobët / e nënkuptuar - Dynamic / e fortë / Python e nënkuptuar - Java dinamike / e fortë / e nënkuptuar - PHP statike / e fortë / e qartë - Dinamike / e dobët / e nënkuptuar C - statike / e dobët / e qartë C++ - Statike / gjysmë e fortë / Perl eksplicite - Dynamic / Dob / Implicit Objective-C - Static / Dob / Explicit C# - Statike / e fortë / Haskell eksplicite - Statike / e fortë / e nënkuptuar Common Lisp - Dinamike / e fortë / e nënkuptuar

Ndoshta kam bërë një gabim diku, veçanërisht me CL, PHP dhe Obj-C, nëse keni një mendim tjetër për ndonjë gjuhë, shkruani në komente.

  • Shtypja dinamike është një teknikë e përdorur gjerësisht në gjuhët e programimit dhe gjuhët e specifikimit, në të cilën një ndryshore shoqërohet me një lloj në momentin e caktimit të një vlere dhe jo në momentin e deklarimit të ndryshores. Kështu, në pjesë të ndryshme të programit, e njëjta variabël mund të marrë vlera të llojeve të ndryshme. Shembuj të gjuhëve me shtypje dinamike janë Smalltalk, Python, Objective-C, Ruby, PHP, Perl, JavaScript, Lisp, xBase, Erlang, Visual Basic.

    Teknika e kundërt është shtypja statike.

    Në disa gjuhë me shtypje dinamike të dobët, ka një problem me krahasimin e vlerave, për shembull, PHP ka operatorë krahasimi "==", "!=" dhe "===", "!==", ku i dyti çifti i operacioneve krahason vlerat dhe llojet e variablave. Operacioni “===” jep të vërtetë vetëm nëse ka një përputhje të plotë, në ndryshim nga “==”, i cili e konsideron të vërtetë shprehjen e mëposhtme: (1=="1"). Vlen të përmendet se ky nuk është problem me shtypjen dinamike në përgjithësi, por me gjuhë programimi specifike.

Konceptet e ndërlidhura

Një gjuhë programimi është një gjuhë zyrtare e krijuar për të shkruar programe kompjuterike. Një gjuhë programimi përcakton një grup rregullash leksikore, sintaksore dhe semantike që përcaktojnë pamjen e programit dhe veprimet që interpretuesi (zakonisht një kompjuter) do të kryejë nën kontrollin e tij.

Sheqeri sintaksor në një gjuhë programimi është veçori sintaksore, përdorimi i të cilave nuk ndikon në sjelljen e programit, por e bën përdorimin e gjuhës më të përshtatshëm për njerëzit.

Një veti është një mënyrë për të hyrë në gjendjen e brendshme të një objekti, duke simuluar një ndryshore të një lloji. Hyrja në një veçori të një objekti duket njësoj si qasja në një fushë strukture (në programimin e strukturuar), por në të vërtetë zbatohet përmes një thirrjeje funksioni. Kur përpiqeni të vendosni vlerën e një vetie të caktuar, thirret një metodë, dhe kur përpiqeni të merrni vlerën e kësaj vetie, thirret një metodë tjetër.

Extended Backus–Naur Form (EBNF) është një sistem formal për përcaktimin e sintaksës në të cilin disa kategori sintaksore përcaktohen në mënyrë sekuenciale përmes të tjerave. Përdoret për të përshkruar gramatikat formale pa kontekst. Sugjeruar nga Niklaus Wirth. Është një përpunim i zgjeruar i formave Backus-Naur, ndryshon nga BNF në dizajne më "kapacitet", duke lejuar, me të njëjtën aftësi shprehëse, të thjeshtojë...

Programimi aplikativ është një lloj programimi deklarativ në të cilin shkrimi i një programi konsiston në aplikimin sistematik të një objekti në një tjetër. Rezultati i një aplikacioni të tillë është përsëri një objekt që mund të marrë pjesë në aplikacione edhe si funksion edhe si argument, e kështu me radhë. Kjo e bën shënimin e programit matematikisht të qartë. Fakti që një funksion shënohet me një shprehje tregon mundësinë e përdorimit të funksioneve të vlerave - funksionale...

Një gjuhë programimi lidhëse është një gjuhë programimi e bazuar në idenë se lidhja e dy pjesëve të kodit shpreh përbërjen e tyre. Në një gjuhë të tillë, treguesi i nënkuptuar i argumenteve të funksionit përdoret gjerësisht (shih programimin e pakuptimtë), funksionet e reja përcaktohen si një përbërje funksionesh dhe lidhja përdoret në vend të aplikimit. Kjo qasje është në kontrast me programimin aplikativ.

Një variabël është një atribut i një sistemi fizik ose abstrakt që mund të ndryshojë vlerën e tij, zakonisht numerike. Koncepti i një variabli përdoret gjerësisht në fusha të tilla si matematika, shkenca, inxhinieria dhe programimi. Shembuj të variablave përfshijnë: temperaturën e ajrit, parametrin e funksionit dhe shumë më tepër.

Analiza sintaksore (ose analizimi, analizimi i zhargonit ← analizimi i anglishtes) në gjuhësi dhe shkenca kompjuterike është procesi i krahasimit të një sekuence lineare leksemash (fjalë, shenja) të një gjuhe natyrore ose formale me gramatikën e saj formale. Rezultati është zakonisht një pemë analizuese (pema sintaksore). Zakonisht përdoret në lidhje me analizën leksikore.

Një lloj i përgjithësuar i të dhënave algjebrike (GADT) është një nga llojet e llojeve të të dhënave algjebrike, i cili karakterizohet nga fakti se konstruktorët e tij mund të kthejnë vlera të ndryshme nga lloji i lidhur me të. Projektuar nën ndikimin e punimeve në familjet induktive midis studiuesve të llojeve të varura.

Semantika në programim është një disiplinë që studion formalizimin e kuptimeve të konstrukteve të gjuhëve programuese përmes ndërtimit të modeleve të tyre formale matematikore. Mjete të ndryshme mund të përdoren si mjete për ndërtimin e modeleve të tilla, për shembull, logjika matematikore, llogaritja λ, teoria e grupeve, teoria e kategorive, teoria e modelit dhe algjebra universale. Formalizimi i semantikës së një gjuhe programimi mund të përdoret për të përshkruar gjuhën, për të përcaktuar vetitë e gjuhës ...

Programimi i orientuar nga objekti (OOP) është një metodologji programimi e bazuar në paraqitjen e një programi si një koleksion objektesh, secila prej të cilave është një shembull i një klase specifike dhe klasat formojnë një hierarki trashëgimore.

Një ndryshore dinamike është një variabël në një program, hapësira në RAM për të cilën ndahet gjatë ekzekutimit të programit. Në thelb, është një pjesë e memories që i ndahet nga sistemi një programi për qëllime specifike gjatë kohës që programi është në punë. Kështu ndryshon nga një ndryshore statike globale - një pjesë memorie e alokuar nga sistemi në një program për qëllime specifike përpara se programi të fillojë të funksionojë. Një ndryshore dinamike është një nga klasat e variablave të kujtesës.