Parametrický polymorfismus v programovacích jazycích a teorii typů je vlastnost sémantiky typového systému , která umožňuje zpracovávat hodnoty různých typů identickým způsobem, to znamená fyzicky spouštět stejný kód pro data různých typů. [1] [2] .
Parametrický polymorfismus je považován za „opravdovou“ formu polymorfismu [3] , díky čemuž je jazyk výraznější a výrazně zvyšuje opětovné použití kódu . Tradičně se staví proti ad-hoc-polymorfismu [1] , který poskytuje jediné rozhraní pro potenciálně odlišný kód pro různé typy povolené v daném kontextu, potenciálně nekompatibilní ( staticky nebo dynamicky ). V řadě kalkulů, jako je teorie kvalifikovaných typů , je ad-hoc polymorfismus považován za speciální případ parametrického polymorfismu.
Parametrický polymorfismus je základem typových systémů jazyků v rodině ML ; systémy tohoto typu se nazývají polymorfní. V komunitách jazyků s nepolymorfními typovými systémy (potomci Algol a BCPL [4] ) se parametricky polymorfní funkce a typy nazývají „ zobecněné “.
Termín „ parametrický polymorfismus “ se tradičně používá pro typově bezpečný parametrický polymorfismus, ačkoli existují i jeho netypizované formy (viz parametrický polymorfismus v C a C++ ) [4] . Klíčovým konceptem typově bezpečného parametrického polymorfismu je vedle polymorfní funkce polymorfní typ .
Polymorfní typ ( angl. polymorphic type ) nebo polytype ( angl. polytype ) je typ parametrizovaný jiným typem. Parametr v typové vrstvě se nazývá typová proměnná (nebo typová proměnná) .
Formálně je typový polymorfismus studován v polymorfně typovaném lambda kalkulu , nazývaném Systém F.
Například funkci appendzřetězení dvou seznamů do jednoho lze sestavit bez ohledu na typ prvků seznamu. Nechť proměnná type a popisuje typ prvků seznamu. Potom lze funkci appendzadat jako " forall a. [a] × [a] -> [a]" (zde konstrukce [a]znamená typ " seznam, jehož každý prvek má typa " - syntaxe přijatá v jazyce Haskell ). V tomto případě se říká, že typ je parametrizován proměnnou apro všechny hodnoty a. V každém místě aplikace appendna konkrétní argumenty je hodnota avyřešena a každá její zmínka v podpisu typu je nahrazena hodnotou odpovídající kontextu aplikace. V tomto případě tedy podpis typu funkce vyžaduje, aby typy prvků obou seznamů a výsledek byly totožné .
Sada platných hodnot pro typovou proměnnou je dána kvantifikací . Nejjednodušší kvantifikátory jsou univerzální (jako v příkladu s append) a existenciální (viz níže).
Kvalifikovaný typ je polymorfní typ, navíc vybavený sadou predikátů , které regulují rozsah platných hodnot pro parametr tohoto typu. Jinými slovy, kvalifikace typu vám umožňuje řídit kvantifikaci libovolným způsobem. Teorii kvalifikovaných typů vybudoval Mark P. Jones v roce 1992 [5] . Poskytuje společné zdůvodnění pro nejexotičtější typové systémy, včetně typových tříd , rozšiřitelných zápisůa podtypů , a umožňuje přesné formulování specifických omezení pro konkrétní polymorfní typy, čímž vytváří vztah mezi parametrickými a ad-hoc polymorfismus ( přetěžování ) a mezi explicitním a implicitním přetěžováním. Spojení typu s predikátem v této teorii senazývá důkaz . Algebra podobná lambda kalkulu je formulována pro důkazy , včetně abstrakce důkazů, aplikace důkazů atd. Korelace termínu této algebry s výslovně přetíženou funkcíse nazývá překlad důkazů .
Motivujícími příklady pro vývoj zobecněné teorie byly třídy typu Wadler-Blott a typizace rozšiřitelných záznamů prostřednictvím predikátů Harper-Pearce [5] [6] .
Parametricky polymorfní typové systémy jsou zásadně klasifikovány podle hodnosti a predikativní vlastnosti . Kromě toho se rozlišuje explicitní a implicitní polymorfismus [7] a řada dalších vlastností. Implicitní polymorfismus je poskytován prostřednictvím odvození typu , což výrazně zlepšuje použitelnost, ale má omezenou expresivitu. Mnoho praktických parametricky polymorfních jazyků odděluje fáze externího implicitně typovaného jazyka a interního explicitně typovaného jazyka .
Nejobecnější formou polymorfismu je „ nepredikativní polymorfismus vyšší úrovně “. Nejoblíbenějšími omezeními této formy jsou polymorfismus 1. úrovně nazývaný " prenex " a predikativní polymorfismus . Společně tvoří " predikativní prenexový polymorfismus " podobný tomu implementovanému v ML a dřívějších verzích Haskellu .
Jak se typové systémy stávají složitějšími, stávají se typové signatury tak složité, že jejich úplné nebo téměř úplné odvození je mnohými výzkumníky považováno za kritickou vlastnost, jejíž absence způsobí, že jazyk nebude vhodný pro praxi [8] [9] . Například u tradičního kombinátoru má mapúplný typový podpis (s přihlédnutím ke generické kvantifikaci) v rámci typově bezpečného sledování toku výjimek následující formu [10] [8] (jak je uvedeno výše , znamená seznam prvky typu ):[a]a
Pořadí polymorfismu ukazujehloubku vnoření kvantifikátorů typových proměnných povolených v rámci systému . " Polymorfismus hodnosti k " (pro k > 1 ) umožňuje specifikovat typové proměnné podle polymorfních typů hodnosti ne vyšší než ( k - 1) . " Polymorfismus vyšších řádů " umožňuje umístit kvantifikátory typových proměnných nalevo od libovolného počtu šipek v typech vyšších řádů .
Joe Wells dokázal [ 11] , že odvození typu pro systém F typu Curry není rozhodnutelné pro hodnosti nad 2, takže při použití vyšších hodností musí být použita explicitní anotace typu [12] .
Existují systémy částečného inferenčního typu , které vyžadují, aby byly anotovány pouze proměnné typu , které nelze odvodit [13] [14] [15] .
Polymorfismus PrenexPolymorfismus úrovně 1 se často nazývá prenexový polymorfismus (od slova "prenex" - viz prenex normální forma ). V prenexovém polymorfním systému nelze typové proměnné konkretizovat pomocí polymorfních typů. Toto omezení činí rozlišení mezi monomorfními a polymorfními typy zásadním, a proto se v systému prenex polymorfní typy často nazývají „ typovací schémata “ ( anglicky typová schémata ), aby se odlišily od „obyčejných“ (monomorfních) typů (monotypů). V důsledku toho mohou být všechny typy zapsány ve formě, když jsou všechny kvantifikátory typových proměnných umístěny na nejvzdálenější (prenex) pozici, která se nazývá prenexová normální forma . Jednoduše řečeno, definice polymorfních funkcí jsou povoleny, ale polymorfní funkce nelze předávat jako argumenty jiným funkcím [16] [17] - polymorfně definované funkce musí být před použitím monotypově konkretizovány.
Blízkým ekvivalentem je takzvaný „ Let-polymorfismus “ nebo „ polymorfismus ve stylu ML “ od Damas-Milnera. Technicky má Let-polymorfismus v ML další syntaktická omezení, jako je „ omezení hodnoty “ spojené s problémy s bezpečností typu při použití odkazů (které se nevyskytují v čistých jazycích, jako je Haskell a Clean ) [18] [19] .
Predikativní (podmíněný) polymorfismus vyžaduje, aby typová proměnná byla konkretizována monotypem (nikoli polytypem).
Predikativní systémy zahrnují intuicionistickou teorii typů a Nuprl .
Nepredikativní polymorfismusImpredikativní (nepodmíněný) polymorfismus umožňuje vytvořit instanci typové proměnné s libovolným typem – jak monomorfním, tak polymorfním, včetně typu, který je definován. (Umožnění, aby systém v rámci kalkulu rekurzivně zahrnoval sám sebe do sebe, se nazývá impredikativita . Potenciálně to může vést k paradoxům jako Russell nebo Cantor [20] , ale to se nestane v případě propracovaného typového systému [21] .)
Nepredikativní polymorfismus umožňuje předávat polymorfní funkce jiným funkcím jako parametry, vracet je jako výsledek, ukládat je do datových struktur atd., proto se také nazývá prvotřídní polymorfismus . Toto je nejvýkonnější forma polymorfismu, ale na druhé straně představuje vážný problém pro optimalizaci a činí odvození typu neřešitelným .
Příkladem impredikativního systému je Systém F a jeho rozšíření (viz lambda kostka ) [22] .
Tradičně v potomcích ML může být funkce polymorfní pouze při pohledu „zvenčí“ (to znamená, že ji lze aplikovat na argumenty různých typů), ale její definice může obsahovat pouze monomorfní rekurzi (tj. rozlišení typu je provedené před hovorem). Rozšíření rekonstrukce typu ML na rekurzivní typy nepředstavuje žádné vážné potíže. Na druhou stranu kombinace rekonstrukce typu s rekurzivně definovanými pojmy vytváří obtížný problém známý jako polymorfní rekurze . Mycroft a Meertens navrhli polymorfní typovací pravidlo, které umožňuje instanci rekurzivních volání rekurzivní funkce z jejího vlastního těla s různými typy. V tomto kalkulu, známém jako Milner-Mycroftův kalkul, je odvození typu nerozhodnutelné . [23]
Typy produktů (také známé jako „ záznamy “) slouží jako formální základ pro objektově orientované a modulární programování. Jejich duální pár se skládá ze součtových typů (také známých jako " varianty ") [24] [25] [19] :
Společně jsou prostředkem k vyjádření jakýchkoli složitých datových struktur a některých aspektů chování programů .
Record calculi je zobecněný název pro problém a řadu jeho řešení týkajících se flexibility typů produktů v programovacích jazycích pod podmínkou typové bezpečnosti [26] [27] [28] . Tento termín se často rozšiřuje na typy součtů a hranice pojmu " typ záznamu " se mohou lišit počet od počtu (stejně jako samotný pojem " typ ". Používají se také termíny „ polymorfismus záznamu “ (který opět často zahrnuje variantní polymorfismus ) [27] , „ modulový počet “ [9] a „ strukturní polymorfismus “.
Typové součiny a součty ( záznamy a varianty [ en ] poskytují flexibilitu při vytváření složitých datových struktur, ale omezení mnoha systémů reálného typu často brání jejich použití ve skutečné flexibilitě. Tato omezení obvykle vznikají kvůli problémům s efektivitou, odvozením typu nebo jednoduše použitelností. [29]
Klasickým příkladem je Standard ML , jehož typový systém byl záměrně omezen tak, aby se snoubila snadnost implementace s výrazností a matematicky prokazatelnou typovou bezpečností .
Příklad definice záznamu :
> val r = { name = "Foo" , used = true }; (* val r : {name : string, used : bool} = {name = "Foo", used = true} *)Přístup k hodnotě pole podle jeho názvu se provádí pomocí prefixové konstrukce formuláře #field record:
> val r1 = #jméno r ; (* val r1 : string = "Foo" *)Následující definice funkce nad záznamem je správná:
> fun name1 ( x : { name : string , age : int }) = # name xA následující generuje chybu kompilátoru, že typ není plně vyřešen :
> fun name2 x = #name x (* nevyřešený typ v deklaraci: {name : '1, ...} *)Monomorfismus záznamů je činí neflexibilními, ale účinnými [30] : protože skutečné umístění v paměti každého pole záznamu je známo předem (v době kompilace), odkazování na něj jménem se zkompiluje do přímého indexování, které se obvykle vypočítává na jednom počítači. návod. Při vývoji složitých škálovatelných systémů je však žádoucí mít možnost abstrahovat pole z konkrétních záznamů – například definovat univerzální selektor polí:
zábava vyberte r l = #l rKompilace polymorfního přístupu k polím, která mohou být v různých záznamech v různém pořadí, je ale obtížný problém, a to jak z pohledu bezpečnostní kontroly operací na jazykové úrovni, tak z pohledu výkonu na úrovni strojového kódu. Naivním řešením může být dynamické vyhledávání slovníku při každém volání (a skriptovací jazyky jej používají), ale to je zjevně extrémně neefektivní. [31]
Typové součty tvoří základ větveného výrazu a vzhledem k přísnosti typového systému poskytuje kompilátor kontrolu nad úplností analýzy. Například pro následující typ součtu :
datový typ 'a foo = A z 'a | B z ( 'a * 'a ) | Cjakákoli funkce nad tím bude vypadat
zábavný bar ( p : 'a foo ) = případ p z A x => ... | B ( x , y ) => ... | c => ...a když jsou některé klauzule odstraněny, kompilátor vydá varování o neúplné analýze (" match nonexhaustive"). Pro případy, kdy pouze několik z mnoha možností vyžaduje analýzu v daném kontextu, je možné uspořádat defaultvětvení pomocí tzv. "Joker" - univerzální vzorek, se kterým jsou všechny platné (podle psaní) hodnoty srovnatelné. Píše se s podtržítkem (" _"). Například:
zábavný bar ( p : 'a foo ) = případ p z C => ... | _ => ...V některých jazycích (ve Standard ML , v Haskell ) je i „jednodušší“ konstrukce if-then-elsejen syntaktický cukr nad speciálním případem větvení , tedy výraz
když A , tak B jinak Cjiž ve fázi gramatické analýzy je prezentován ve formě
případ A pravdy = > B | nepravda => Cnebo
případ A pravdy = > B | _ => CVe standardním ML jsou záznamy a varianty monomorfní, nicméně je podporován syntaktický cukr pro práci se záznamy s více poli, nazývaný " univerzální vzor " [32] :
> val r = { name = "Foo" , used = true }; (* val r : {jméno : řetězec, použité : bool} = {jméno = "Foo", použité = pravda} *) > val { použité = u , ...} = r ; (* val u : bool = true *)Tři tečky v typu „ {used, ...}“ znamenají, že v tomto záznamu existují kromě zmíněných i další pole. Neexistuje však žádný polymorfismus záznamu jako takový (ani prenex ): je vyžadováno plné statické rozlišení informace o typu monomorfního záznamu (přes odvození nebo explicitní anotaci ).
Pojem rozšiřitelné záznamy se používá pro zobecněné označení takových operací jako expanze (vytvoření nového záznamu na základě existujícího s přidáním nových polí), řezání (převzetí určené části z existujícího záznamu) atd. z toho vyplývá možnost tzv. „ aktualizace funkčního záznamu “ ( aktualizace funkčního záznamu ) - operace sestrojení nové hodnoty záznamu na základě stávajícího zkopírováním názvů a typů jeho polí, ve kterých je jedno nebo více polí v nový záznam obdrží nové hodnoty, které se liší od původních (a případně mají jiný typ). [33] [34] [19] [35] [36] [37]
Operace funkční aktualizace a rozšíření jsou samy o sobě ortogonální pro záznam polymorfismu, ale jejich polymorfní použití je zvláště zajímavé. Dokonce i u monomorfních záznamů je důležité mít možnost vynechat explicitní zmínku o polích, která jsou zkopírována beze změny, což ve skutečnosti představuje polymorfismus záznamu v čistě syntaktické formě . Na druhou stranu, pokud považujeme všechny záznamy v systému za rozšiřitelné, pak to umožňuje, aby funkce na záznamech byly typovány jako polymorfní.
Příklad nejjednodušší varianty návrhu je implementován v Alice ML (podle současných nástupnických konvencí ML ) [36] . Univerzální vzor (elipsa ) má rozšířené možnosti: lze jej použít k „zachycení řádku“, aby bylo možné pracovat se „zbývající“ částí záznamu jako hodnotou:
> val r = { a = 1 , b = pravda , c = "ahoj" } (* r = {a = 1, b = pravda, c = "ahoj"} *) > val { a = n , ... = r1 } = r (* r1 = {b=pravda, c="ahoj"} *) > hodnota r2 = { d = 3,14 , ... = r1 } (* r2 = {b=pravda, c="ahoj ", d=3,14} *)Funkční aktualizace je implementována jako derivát zachycení řádku se servisním slovem where. Například následující kód:
> nechť hodnota r = { a = 1 , c = 3,0 , d = "není seznam" , f = [ 1 ], p = [ "není řetězec" ], z = NONE } v { r , kde d = nula , p = "ahoj" } konecse automaticky přepíše do tvaru:
> nechť val r = { a = 1 , c = 3,0 , d = "není seznam" , f = [ 1 ], p = [ "není řetězec" ], z = ŽÁDNÝ } val { d = _, p = _, ... = tmp } = r v { ... = tmp , d = nula , p = "ahoj" } konecJeden z prvních (konec 80. let - začátek 90. let 20. století ) navrhoval různé možnosti pro formalizaci rozšiřitelných záznamů pomocí operací zřetězení na nepolymorfních záznamech (Harper-Pearce [38] , Wand [39] , Salzmann [40] ). Cardelli dokonce v jazyce Amber používal místo polymorfismu záznamové operace . Neexistuje žádný známý způsob, jak tyto výpočty efektivně sestavit; tyto výpočty jsou navíc velmi složité z hlediska typově teoretické analýzy. [27] [41] [42] [43]
Například [33] :
val car = { name = "Toyota" ; věk = "starý" ; id = 6678 } val truck = { name = "Toyota" ; id = 19823235 } ... val repaired_truck = { osobní a nákladní automobil }polymorfismu řádků ) ukázal, že vícenásobnou dědičnost lze modelovat pomocí zřetězení záznamů [39] [33] .
Luca Cardelli prozkoumal možnostformalizace „ polymorfismu záznamu “ prostřednictvím vztahů podtypů na záznamech: k tomu je záznam považován za „univerzální podtyp“, to znamená, že jeho hodnota může odkazovat na celý soubor jeho supertypů. Tento přístup také podporuje dědičnost metod a slouží jako typově teoretický základ pro nejběžnější formy objektově orientovaného programování . [27] [44] [45]
Cardelli představil variaci na metodu kompilace polymorfismu záznamu napříč jejich podtypy předdefinováním offsetu všech možných štítků v potenciálně obrovské struktuře s mnoha prázdnými sloty [31] .
Metoda má značné nevýhody. Podpora podtypování v typovém systému značně komplikuje mechanismus kontroly typové konzistence [46] . Navíc v jeho přítomnosti přestává statický typ výrazu poskytovat úplnou informaci o dynamické struktuře hodnoty položky . Pokud například používáte pouze podtypy, následující výraz:
> pokud je pravda , pak { A = 1 , B = pravda } else { B = nepravda , C = "Kočka" } (* val it : {B : bool} *)má typ {B : bool}, ale jeho dynamická hodnota je rovna {A = 1, B = true}, to znamená, že se informace o typu rozšířeného záznamu ztratí [43] , což je vážný problém pro kontrolu operací, které pro své provedení vyžadují kompletní informace o struktuře hodnot (jako např. srovnání pro rovnost ) [19] . Konečně, v přítomnosti podtypů výběr mezi uspořádanou a neuspořádanou reprezentací záznamů vážně ovlivňuje výkon [47] .
Popularita podtypování je způsobena tím, že poskytuje jednoduchá a vizuální řešení mnoha problémů. Například objekty různých typů mohou být umístěny do jednoho seznamu, pokud mají společný nadtyp [48] .
Mitchell Wand v roce 1987 navrhl myšlenku(ne výslovně specifikované) části záznamu prostřednictvím toho, co nazval proměnná typu řádku ( proměnná typu řádku ) [49] .
Řádková proměnná je proměnná typu , která prochází množinou konečných sad (řádků) typovaných polí (párů " (значение : тип)"). Výsledkem je schopnost implementovat širokou škálu dědičnosti přímo nad parametrickým polymorfismem, který je základem ML , aniž by se typový systém komplikoval pravidly pro podtypování Výsledný druh polymorfismu se nazývá řádkový polymorfismus . Inline polymorfismus se vztahuje jak na součiny typů , tak na součty typů .
Vand si tento termín vypůjčil z angličtiny. řádek (řádek, řetěz, čára) z Algol-68 , kde to znamenalo soubor pohledů. V ruskojazyčné literatuře se tento termín v kontextu Algolu tradičně překládá jako „vícedruhové“. Existuje také překlad „řádkových proměnných“ jako „řetězcové proměnné“ [41] , což může způsobit zmatek s malými písmeny v typech řetězců .
Příklad ( jazyk OCaml ; syntaxe postfixu, record#field) [50] :
# let send_m a = a # m ;; (* hodnota send_m : < m : a; .. > -> a = <zábava> *)Tři tečky (dvou teček) jsou akceptovanou syntaxí OCaml pro nepojmenovanou proměnnou typu inline . Díky tomuto typování lze funkci send_maplikovat na objekt libovolného (dříve neznámého ) typu objektu, který zahrnuje metodu modpovídajícího podpisu.
Odpočet typu pro Wanda kalkul v původní verzi byl neúplný: kvůli chybějícím omezením pro rozšíření série nahradí přidání pole, pokud se název shoduje, stávající - v důsledku toho ne všechny programy mají hlavní typ [6] [43] . Tento systém však byl prvním konkrétním návrhem na rozšíření ML o záznamy, které podporují dědičnost [51] . V následujících letech byla navržena řada různých vylepšení, včetně těch, díky nimž je kompletní [51] [27] .
Nejpozoruhodnější stopu zanechal Didier Remy ( francouzsky Didier Rémy ). Vybudoval praktický typový systém s rozšiřitelnými záznamy, včetně kompletního a efektivního algoritmu rekonstrukce typu [52] [53] . Remy stratifikuje jazyk typů na druhy a formuluje seřazenou algebru typů ( angl. sorted algebra of types, sorted language of types ). Rozlišuje se mezi vlastním druhem typů (včetně typů polí) a druhem polí ; jsou zavedena mapování mezi nimi a na jejich základě jsou formulována pravidla pro typování rozšířených záznamů jako jednoduché rozšíření klasických pravidel ML . Informace o přítomnosti pole jsou definovány jako mapování z řazení typu na řazení podle pole . Proměnné typu řádku jsou přeformulovány jako proměnné náležející k řazení pole a rovnající se konstantě absence , což je prvek řazení pole , který nemá shodu v řazení typu . Operace vyhodnocení typu pro záznam n polí je definována jako mapování n-árního pole na typ (kde každé pole v n-tice je buď vypočítáno funkcí přítomnosti nebo dáno konstantou absence ).
Zjednodušeně lze myšlenku kalkulu interpretovat jako rozšíření typu libovolného pole záznamu o příznak přítomnosti/nepřítomnosti a reprezentaci záznamu jako n-tice se slotem pro každé možné pole [6] . V implementačním prototypu byla syntaxe typového jazyka přiblížena typově teoretické formulaci, například [52] :
# let car = { name = "Toyota" ; věk = "starý" ; id = 7866 } ;; (* auto : ∏ (jméno : pre (řetězec); id : pre (číslo); věk : před (řetězec); abs) *) # let truck = { name = "Blazer" ; id = 6587867567 } ;; (* truck : ∏ (name : pre (string); id : pre (num); abs) *) # let person = { name = "Tim" ; věk = 31 ; id = 5656787 } ;; (* osoba : ∏ (jméno : pre (řetězec); id : pre (num); věk : pre (num); abs) *)(symbol ∏v Remy znamená operaci výpočtu typu)
Přidání nového pole se zapisuje pomocí konstrukce with:
# nechat řidiče = { osoba s vozidlem = auto } ;; (* řidič : ∏ (vozidlo : pre (∏ (jméno : pre (řetězec); id : pre (číslo); věk : pre (řetězec); abs)); jméno : pre (řetězec); id : pre (číslo) ; věk : pre (num); abs) *)Funkční aktualizace je napsána identicky, s tím rozdílem, že zmínka o již existujícím poli ji přepíše:
# let truck_driver = { řidič s vozidlem = truck };; (* řidič kamionu : ∏ (vozidlo : pre (∏ (jméno : pre (řetězec); id : pre (num); abs)); jméno : pre (řetězec); id : pre (num); věk : pre (číslo ); břišní svaly) *)Toto schéma formalizuje omezení potřebná pro kontrolu operací na záznamech a odvození hlavního typu, ale nevede ke zřejmé a efektivní implementaci [6] [43] . Remy používá hashování, které je v průměru poměrně efektivní, ale zvyšuje provozní režii i u nativně monomorfních programů a není vhodné pro záznamy s mnoha poli [31] .
Rémy pokračoval ve zkoumání použití inline polymorfismu ve spojení s datovým podtypováním , přičemž zdůraznil, že se jedná o ortogonální koncepty, a ukázal, že záznamy se stávají nejvýraznějšími, když se používají společně [54] . Na tomto základě spolu s Jérômem Vouillonem navrhl odlehčené objektově orientované rozšíření ML [55] . Toto rozšíření bylo implementováno v jazyce Xaviera Leroye "Caml Special Light" a přeměnilo jej na OCaml [56] . Objektový model OCaml je úzce provázán s využitím strukturálního podtypování a inline polymorfismu [48] , proto jsou někdy mylně identifikovány. Inline produktový polymorfismus v OCaml je jádrem odvození typu ; vztahy podtypů nejsou v podporovaném jazyce nutné, ale dále zvyšují flexibilitu v praxi [55] [50] [48] . OCaml má jednodušší a popisnější syntaxi pro informace o typu.
Jacques Garrigue ( fr. Jacques Garrigue ) zavedl [25] praktický systém polymorfních součtů . Zkombinoval teoretickou práci Remiho a Ohoriho , vybudoval systém, který běží uprostřed: informace o přítomnosti značek v záznamu jsou reprezentovány pomocí pohlaví a informace o jejich typech využívají inline proměnné. Aby kompilátor rozlišil mezi polymorfními a monomorfními součty, používá Garriga speciální syntaxi (backtick, prefix tag). To eliminuje potřebu deklarace typu – můžete na něj okamžitě zapisovat funkce a kompilátor vypíše minimální seznam značek, jakmile se tyto funkce skládají. Tento systém se stal součástí OCaml kolem roku 2000 , ale ne místo , ale navíc k monomorfním součtům , protože jsou poněkud méně efektivní a kvůli neschopnosti kontrolovat úplnost analýzy je obtížné najít chyby (na rozdíl od Bloomova řešení ). [25] [57]
Nevýhodou Wandova inline polymorfismu je nesrozumitelnost implementace (neexistuje jediný systematický způsob, jak jej sestavit, každý specifický typový systém založený na inline proměnných má svou vlastní implementaci) a nejednoznačný vztah s teorií (neexistuje jednotná formulace pro typovou kontrolu a inferenci , podpora inference byla řešena samostatně a vyžadovala experimenty s ukládáním různých omezení) [27] .
Nejsložitějším typem záznamů jsou závislé záznamy. Takové záznamy mohou zahrnovat typy i "běžné" hodnoty (materializované typy, reifikované [9] ) a termíny a typy v dalším pořadí v těle záznamu lze určit na základě těch, které jim předcházejí. . Takové zápisy odpovídají „ slabým součtům “ teorie závislých typů , známé také jako „ existenciály “, a slouží jako nejobecnější zdůvodnění systémů modulů v programovacích jazycích [58] [59] . Cardelli považoval [60] typů podobných vlastnostmi za jeden z hlavních typů v plnotypovém programování (ale nazval je „ n-tice “).
Robert Harper a Mark Lillibridge zkonstruovali [61 ] [59] průsvitný součtový kalkul ,abyformálně ospravedlnili prvotřídní modulový jazyk vyššího řádu , nejpokročilejší známý systém modulů . Tento kalkul se mimo jiné používá v sémantice Harper-Stone , která představuje typově teoretické zdůvodnění Standard ML .
Průhledné součty zobecňují slabé součty prostřednictvím štítků a sady rovností, které popisují konstruktory typů . Pojem průsvitný znamená , že typ záznamu může obsahovat jak typy s explicitně exportovanou strukturou, tak i zcela abstraktní . Vrstva rodů v kalkulu má jednoduché klasické složení: rozlišuje se rod všech typů a funkční rody druhu , typizační typové konstruktory ( ML nepodporuje polymorfismus u vyšších rodů , všechny typové proměnné patří do rodu , a abstrakce typových konstruktorů je možná pouze prostřednictvím funktorů [62 ] ). Kalkulus rozlišuje pravidla pro podtypování pro záznamy jako základní typy a pro pole záznamů jako jejich složky, v tomto pořadí, s ohledem na „podtypy“ a „podpole“, zatímco zakrývání (abstrakce) podpisů polí je samostatný koncept od podtypování. Podtypování zde formalizuje mapování modulů na rozhraní . [61] [9]
Harper-Lilybridgeův kalkul je nerozhodnutelný i z hlediska kontroly typové konzistence ( dialekty modulového jazyka implementované ve Standard ML a OCaml používají omezené podmnožiny tohoto kalkulu). Později však Andreas Rossberg na základě jejich nápadů vybudoval jazyk „ 1ML “, ve kterém jsou tradiční záznamy na úrovni jádra jazyka a struktur na úrovni modulů stejnou prvotřídní konstrukcí [9] (výrazně výraznější než Cardelliho - viz kritika jazyka modulu ML ). Začleněním myšlenky Harpera a Mitchella [63] o rozdělení všech typů do vesmírů „malých“ a „velkých“ typů (zjednodušeně je to podobné základnímu rozdělení typů na jednoduché a agregované typy, přičemž nestejná pravidla pro psaní), Rossberg poskytoval rozlišovací schopnost nejen kontroly konzistence , ale téměř kompletní odvození typu . Navíc 1ML umožňuje nepředvídatelný polymorfismus [64] . Zároveň je vnitřní jazyk v 1ML založen na plochém Systému F ω a nevyžaduje použití závislých typů jako metateorie. Od roku 2015 nechal Rossberg otevřenou možnost přidat inline polymorfismus do 1ML , pouze poznamenal, že by to mělo poskytnout úplnější typové odvození [9] . O rok později přidal [65] efektový polymorfismus k 1ML .
Atsushi Ohori spolu se svým nadřízeným Peterem Bunemanem v roce 1988 navrhl myšlenku omezení rozsahu možných hodnot proměnných běžného typu při polymorfním typování samotných záznamů . Později Ohori tuto myšlenku formalizoval prostřednictvím rodů notace , když v roce 1995 vybudoval plnohodnotný počet a metodu pro jeho efektivní kompilaci [19] [30] . Implementační prototyp vznikl v roce 1992 jako rozšíření kompilátoru SML/NJ [66] , poté Ohori vedl vývoj vlastního kompilátoru SML# , který implementuje stejnojmenný dialekt Standard ML . V SML# slouží polymorfismus záznamů jako základ pro bezproblémové vkládání konstrukcí SQL do programů SML . SML# používají velké japonské společnosti k řešení obchodních problémů spojených s vysoce zatíženými databázemi [67] . Příklad takové relace ( REPL ) [68] :
zábavný bohatý { Plat = s , ... } = s > 100000 ; (* val richy = fn : 'a#{ Salary:int, ... } -> bool *) zábava mladí x = #Věk x < 24 ; (* val young = fn : 'a#{ Age:int, ... } -> bool *) legrace youngAndWealthy x = bohatý x a také mladý x ; (* val youngAndWealthy = fn : 'a#{ Age:int, Plat:int, ... } -> bool *) zábava vybrat display l pred = fold ( fn ( x , y ) => if pred x then ( display x ) ::y else y ) l nil ; (* val select = fn : ('a -> 'b) -> 'a seznam -> ('a -> bool) -> 'b seznam *) zábava youngAndWealthyEmployees l = vybrat # Jméno l youngAndWealthy ; (* val youngAndWealthyEmployees = fn : 'b#{ Age:int, Name:'a, Salary:int, ... } list -> 'a list *)Ohori nazval svůj kalkul „ záznamovým polymorfismem “ ( anglicky record polymorphism ) nebo „ polymorphic record calculus “ ( anglicky polymorphic record calculus ), přičemž zároveň zdůraznil, že stejně jako Wand uvažuje o polymorfismu nejen typů produktů , ale také typů- součty [27] .
Ohori kalkul se vyznačuje nejintenzivnějším využitím rodové vrstvy [6] . V hesle (odkaz typu na rod ) symbol znamená buď rod všech typů ; nebo rod záznamu , který má tvar , označující množinu všech záznamů obsahujících alespoň specifikovaná pole; nebo variantní rod mající tvar označující množinu všech variantních typů obsahujících alespoň specifikované konstruktory . V ploché syntaxi jazyka je omezení typu na nějaký druh zápisu zapsáno jako (viz příklady výše). Řešení je poněkud podobné omezené kvantifikaci Cardelli-Wegnera [27] . t#{...}
Jedinou polymorfní operací poskytovanou tímto kalkulem je operace extrakce pole [69] . Ohori však jako první zavedl jednoduché a efektivní schéma kompilace pro polymorfismus záznamů [43] . Nazval to „kalkulus realizací“ ( implementační kalkul ). Záznam je reprezentován vektorem uspořádaným lexikograficky podle názvů polí původního záznamu; adresování pole jménem se převede na volání zprostředkující funkce, která vrátí číslo daného pole v daném vektoru požadovaným názvem, a následné indexování vektoru podle vypočítaného čísla pozice. Funkce je volána pouze při vytváření instancí polymorfních termínů, což při použití polymorfismu představuje minimální režii na běhové prostředí a při práci s monomorfními typy nezatěžuje žádnou režii. Metoda funguje stejně dobře s libovolně velkými položkami a variantami. Kalkulus poskytuje typovou inferenci a nachází silnou shodu s teorií ( obecná kvantifikace přímo souvisí s obvyklým vektorovým indexováním ), je přímým rozšířením Girard-Reynoldsova lambda kalkulu druhého řádu , který umožňuje různé dobře známé vlastnosti polymorfních typizace, která má být převedena na polymorfismus záznamu [31] .
V praxi nebyla podpora pro polymorfní varianty v SML# implementována kvůli její nekompatibilitě s mechanismem definice typu součtu Standard ML (vyžaduje syntaktické oddělení součtů a rekurzivních typů) [70] .
Nevýhodou ohorského kalkulu je chybějící podpora operací expanze nebo zkrácení záznamu [27] [71] [43] .
V teorii kvalifikovaných typů jsou rozšiřitelné záznamy popsány absencí pole ( "chybí" predikát ) a přítomností predikátu pole ( "má" predikát ). Benedict Gaster ( Benedict R. Gaster ) ji spolu s autorem teorie Markem Jonesem ( Mark P. Jones ) dopracovali z hlediska rozšiřitelných záznamů k praktickému typovému systému implicitně typovaných jazyků včetně definování způsobu kompilace [6] . Razí termín prvotřídní štítky , aby zdůraznili schopnost abstrahovat operace pole od staticky známých štítků. Později Gaster obhájil svou disertační práci [72] o sestrojeném systému .
Gaster-Jonesův kalkul neposkytuje úplné odvození typu . Navíc bylo kvůli problémům s rozhodností zavedeno umělé omezení: zákaz prázdných sérií [73] . Sulzmann se pokusil přeformulovat kalkul [40] , ale systém, který vytvořil, nelze rozšířit tak, aby podporoval expanzi polymorfních záznamů, a nemá univerzální účinnou metodu kompilace [43] .
Daan Leijen přidal do Gaster-Jonesova kalkulu predikát rovnosti řádků (nebo predikát rovnosti řádků ) a přesunul jazyk řad do jazyka predikátů – to zajistilo úplné odvození typu a zrušilo zákaz prázdných řad [74] . Při kompilaci jsou záznamy převedeny do lexikograficky uspořádané n-tice a překlad důkazů je aplikován podle Gaster-Jonesova schématu. Layenův systém umožňuje vyjádření idiomů , jako jsou zprávy vyššího řádu (nejvýkonnější forma objektově orientovaného programování ) a prvotřídní větve .
Na základě těchto prací byla implementována rozšíření do jazyka Haskell [75] .
Výsledky Gaster-Jones se velmi blíží výsledkům z Ohori , a to i přes značné rozdíly v typově teoretickém zdůvodnění a hlavní výhodou je podpora operací expanze a zkrácení záznamu [6] . Nevýhodou kalkulu je, že se spoléhá na vlastnosti typového systému , které se nenacházejí ve většině programovacích jazyků. Odvozování typu pro něj je navíc vážný problém, a proto autoři zavedli další omezení. A ačkoli Layen odstranil mnoho nedostatků, použití -větve není možné. [71]default
S rozvojem softwarových systémů se může počet možností v typu součtu zvýšit a přidání každé možnosti vyžaduje přidání odpovídající větve ke každé funkci nad tímto typem, i když jsou tyto větve v různých funkcích totožné. Složitost zvyšování funkčnosti ve většině programovacích jazyků tedy závisí nelineárně na deklarativních změnách v zadání. Tento vzor je známý jako problém s výrazem . Dalším dobře známým problémem je zpracování výjimek : v průběhu desetiletí výzkumu typových systémů mohly všechny jazyky klasifikované jako typově bezpečné ukončit vyvoláním nezachycené výjimky – protože navzdory psaní samotných výjimek mechanismus pro vyvolání a manipulace s nimi nebyla napsána. Zatímco nástroje pro analýzu toku výjimek byly vytvořeny, tyto nástroje byly vždy externí vůči jazyku.
Matthias Blume , kolega Andrewa Appela pracující na nástupci projektu ML [76] ), jeho postgraduální student Wonseok Chae a kolega Umut Acar vyřešili oba problémy založené na matematických dualitních součinech a součtech . Ztělesněním jejich myšlenek byl jazyk MLPolyR [71] [34] [77] , založený na nejjednodušší podmnožině Standardu ML a doplněný o několik úrovní typové bezpečnosti [78] . Wonseok Chai později obhájil svou disertační práci o těchto úspěších [78] .
Řešení je následující. Podle principu duality odpovídá úvodní forma pro pojem eliminační formě jeho duálu [ 71] . Vylučovací forma součtů (rozbor větví) tedy odpovídá úvodní formě zápisů. To podporuje větvení, aby mělo stejné vlastnosti, které jsou již dostupné pro záznamy – udělejte z nich prvotřídní objekty a dovolte je rozšiřovat.
Například nejjednodušší interpret jazyka výrazů:
fun eval e = případ e of `Konst i => i | `Plus (e1,e2) => eval e1 + eval e2se zavedením prvotřídní konstrukce caseslze přepsat jako:
fun eval e = shodu e s případy `Const i => i | `Plus (e1,e2) => eval e1 + eval e2po kterém caseslze -blok vykreslit:
zábava eval_c eval = případy `Const i => i | `Plus (e1,e2) => eval e1 + eval e2 zábava eval e = shodu e s (eval_c eval)Toto řešení umožňuje default-větvení (na rozdíl od Gaster-Jonesova kalkulu ), které je důležité pro skladbu prvotřídních poboček [34] . Dokončení složení řady se provádí pomocí slova . nocases
fun const_c d = případy `Const i => i výchozí : d zábava plus_c eval d = případy `Plus (e1,e2) => eval e1 + eval e2 výchozí : d fun eval e = shoda e s const_c (plus_c eval nocases ) fun bind env v1 x v2 = if v1 = v2 then x else env v2 fun var_c env d = případy `Var v => env v default : d fun let_c eval env d = případy `Let (v,e,b) => eval (bind env v (eval env e)) b výchozí : d fun eval_var env e = shoda e s const_c (plus_c (eval_var env) (var_c env (let_c eval_var env nocases )))Jak vidíte, nový kód, který je potřeba přidat s kvalitativní složitostí systému, nevyžaduje změnu již napsaného kódu (funkce const_ca plus_c„nic neví“ o následném přidání podpory proměnných a let-bloků do jazykový tlumočník). Prvotřídní rozšiřitelné případy jsou tedy základním řešením problému výrazu , což umožňuje mluvit o rozšiřitelném programovacím paradigmatu [71] [78] . inline polymorfismu , který je již podporován v typovém systému a v tomto smyslu je výhodou takového technického řešení jeho koncepční jednoduchost [ 34] .
Rozšíření softwarových systémů však také vyžaduje kontrolu nad zpracováním výjimek , které mohou být vyvolány v libovolné hloubce vnoření hovorů. A zde Bloom a kolegové prohlašují nový slogan pro bezpečnost typů při vývoji Milnerova sloganu : " Programy, které projdou kontrolou typu , nevyvolávají neošetřené výjimky ." Problém je v tom, že pokud podpis typu funkce obsahuje informace o typech výjimek, které může tato funkce potenciálně vyvolat, a tyto informace v podpisu předané funkce musí být přísně konzistentní s informacemi o parametru funkce vyššího řádu (včetně toto je prázdná množina ) - zadání mechanismu zpracování výjimek okamžitě vyžaduje polymorfismus typů samotných výjimek - jinak přestanou být funkce vyššího řádu polymorfní. Zároveň v bezpečném jazyce jsou výjimky rozšiřitelný součet [79] [80] [81] , tedy variantní typ, jehož konstruktory se přidávají v průběhu programu. V souladu s tím bezpečnost typu toku výjimky znamená potřebu podporovat typy součtů , které jsou rozšiřitelné i polymorfní. I zde je řešením inline polymorfismus .
Stejně jako Garriga kalkul MLPolyR používá speciální syntaxi pro polymorfní součty (backtick, úvodní značka) a není potřeba předdeklarovat typ součtu (to znamená, že výše uvedený kód je celý program, ne fragment). Výhodou je, že není problém s kontrolou úplnosti parsování: sémantika MLPolyR je definována převodem na ověřenou spolehlivost interní jazyk , který nepodporuje ani součtový polymorfismus, ani výjimky (nemluvě o nezachycených výjimkách), takže je potřeba mazání v době kompilace je samo o sobě důkazem spolehlivosti. [34]
MLPolyR využívá netriviální kombinaci několika kalkulů a dvoustupňového překladu. Využívá Remyho kalkul pro dedukce typu a shodu typu , přičemž současně využívá princip duality k reprezentaci součtů jako součinů, poté překládá jazyk do přechodného explicitně typovaného jazyka s polymorfními záznamy a poté používá účinnou kompilační metodu Ohori. . Jinými slovy, Ohoriho kompilační model byl zobecněn tak, aby podporoval Remyho kalkul [69] . Na úrovni teoretického typu zavádí Bloom několik nových syntaktických zápisů najednou, což umožňuje zapisovat pravidla pro výjimky při psaní a prvotřídní větve. Systém typu MLPolyR poskytuje úplné odvození typu , takže autoři upustili od vývoje ploché syntaxe pro explicitní psaní typů a podporu podpisů v jazyce modulu .
Systém typu Leyen také zavádí variantu polymorfismu větví: konstrukt může být reprezentován jako funkce vyššího řádu , která přijímá vstup z funkcí, z nichž každá odpovídá určitému odvětví výpočtu ( vhodná je Haskellova syntaxe pro tuto změnu a nevyžaduje revizi). Například: case
dataList a = nil :: { } | nevýhody :: { hd :: a , tl :: Seznam a } snoc xs r = případ ( reverzní xs ) r poslední xs = snoc xs { nil = \ r -> _ | _ , cons = \ r -> r . hd }Protože záznamy v Layenově systému jsou rozšiřitelné, je analýza větví flexibilní na úrovni dynamických rozhodnutí (jako je řetězení kontrol nebo použití asociativního pole ), ale poskytuje mnohem efektivnější implementaci (označení varianty odpovídá posunu v záznamu). Výchozí podpora větvení ( default) však musí být v tomto případě zrušena, protože jeden default-pattern by odpovídal více polím (a tedy více offsetům). Leyen nazývá tuto konstrukci " prvotřídní vzory " ( prvotřídní vzory ).
Vyšší polymorfismus znamená abstrakci nad typovými konstruktory vyššího řádu, tedy typovými operátory formuláře.
* -> * -> ... -> *Podpora této funkce posouvá polymorfismus na vyšší úroveň a poskytuje abstrakci nad typy i konstruktory typů , stejně jako funkce vyššího řádu poskytují abstrakci nad hodnotami i jinými funkcemi. Polymorfismus ve vyšších rodech je přirozenou součástí mnoha funkčních programovacích idiomů , včetně monád , záhybů a vnořitelných jazyků . [62] [82]
Například [62] , pokud definujete následující funkci ( jazyk Haskell ):
když b m = if b , pak m jinak vrátit ()pak pro něj bude odvozen následující funkční typ :
když :: forall ( m :: * -> * ) . Monad m => Bool -> m () -> m ()Signatura m :: * -> *říká, že typová proměnná m je typová proměnná patřící do vyššího druhu ( anglicky high-kinded type variable ). To znamená, že abstrahuje přes typové konstruktory (v tomto případě unární , jako je Maybenebo []), které lze použít na konkrétní typy, jako je Intnebo (), ke konstrukci nových typů.
V jazycích, které podporují úplnou typovou abstrakci ( Standard ML , OCaml ), musí být všechny typové proměnné rodu * , jinak by byl typový systém nebezpečný . Polymorfismus ve vyšších rodech je tedy zajištěn samotným mechanismem abstrakce v kombinaci s explicitní anotací při vytváření instance, což je poněkud nepohodlné. Idiomatická implementace polymorfismu ve vyšších rodech je však možná bez nutnosti explicitní anotace – k tomu se na typové úrovni používá technika podobná defunkcionalizaci . [62]
Laskavé systémy zajišťují bezpečnost samotných typových systémů tímumožňují kontrolu nad významem typových výrazů .
Nechť je například požadováno implementovat místo obvyklého typu " vector " (lineární pole) rodinu typů " length vectorn ", jinými slovy definovat typ " vector indexed by length ". Klasická implementace Haskellu vypadá takto [83] :
data Nulová data Succ n data Vec :: * -> * -> * kde Nil :: Vec a Zero Cons :: a -> Vec a n -> Vec a ( Succ n )Zde jsou nejprve definovány fantomové typy [84] , tedy typy, které nemají dynamickou reprezentaci. Konstruktory Zero a Succslouží jako "hodnoty typové vrstvy" a proměnná nvynucuje nerovnost různých typů betonu konstruovaných konstruktorem Succ. Typ Vecje definován jako zobecněný algebraický datový typ (GADT).
Řešení podmíněně předpokládá, že fantomový typ nbude použit k modelování celočíselného parametru vektoru na základě Peanových axiomů - tedy budou sestaveny pouze výrazy jako Succ Zero, Succ Succ Zero, atd. Nicméně, ačkoli jsou definice psány v typovém jazyce Succ Succ Succ Zero, samy jsou formulovány netypizovaným způsobem. To je patrné ze signatury Vec :: * -> * -> *, což znamená, že konkrétní typy předávané jako parametry patří do rodu * , což znamená, že se může jednat o jakýkoli konkrétní typ. Jinými slovy, nesmyslné výrazy typu jako Succ Boolnebo zde nejsou zakázány Vec Zero Int. [83]
Pokročilejší výpočet by umožnil přesněji specifikovat rozsah parametru typu:
údaje Nat = nula | Succ Nat data Vec :: * -> Nat -> * kde VNil :: Vec a Zero VCons :: a -> Vec a n -> Vec a ( Succ n )Ale takovou expresivitu mají obvykle pouze vysoce specializované systémy se závislými typy [85] implementované v jazycích jako Agda , Coq a další. Například z hlediska jazyka Agda by zápis Vec :: * -> Nat -> *znamenalo, že rod typu Vec závisí na typu Nat(to znamená, že prvek jednoho druhu závisí na prvku jiného, nižšího druhu ).
V roce 2012 bylo vybudováno rozšíření jazyka Haskell [83] , které implementuje pokročilejší systém pohlaví a činí z výše uvedeného kódu správný haskellský kód. Řešením je, že všechny typy (za určitých omezení) jsou automaticky „ povýšeny “ ( angl. promotion ) na vyšší úroveň, čímž se vytvoří stejnojmenné rody, které lze explicitně použít. Z tohoto pohledu záznam není závislý - znamená pouze, že druhý parametr vektoru musí patřit do jmenovaného rodu a v tomto případě je jediným prvkem tohoto rodu stejnojmenný typ. Vec :: * -> Nat -> *Nat
Řešení je vcelku jednoduché, jak z hlediska implementace v kompilátoru, tak z hlediska praktické dostupnosti. A protože typový polymorfismus je neodmyslitelně přirozeným prvkem Haskellovy sémantiky , propagace typu vede k laskavému polymorfismu , který zvyšuje opětovné použití kódu a poskytuje vyšší úroveň bezpečnosti typu . Například následující GADT se používá k ověření rovnosti typu:
data EqRefl a b kde Refl :: EqRefl a amá pohlaví v klasickém Haskell * -> * -> *, což brání jeho použití k testování rovnosti typových konstruktorů , jako je Maybe. Systém rodu založený na podpoře typu odvozuje polymorfní rod , díky čemuž je typ generičtější . To lze napsat explicitně: forall X. X -> X -> *EqRefl
data EqRefl ( a :: X ) ( b :: X ) kde Refl :: pro všechny X . forall ( a :: X ) . EqRe fl aSystémy efektů navrhli80. let [ 86] [87] [88] s cílem izolovat vedlejší efekty pro jemnější kontrolu nad bezpečností a účinností v konkurenčním programování .
V tomto případě polymorfismus efektu znamená kvantifikaci čistoty specifické funkce , to znamená zahrnutí příznaku do funkčního typu , který charakterizuje funkci jako čistou nebo nečistou. Toto rozšíření pro psaní umožňuje abstrahovat čistotu funkcí vyššího řádu od čistoty funkcí, které jim byly předány jako argumenty.
To je zvláště důležité při přechodu na funkce přes moduly ( záznamy , které obsahují abstraktní typy ) - funktory - protože v podmínkách čistoty mají právo být aplikovatelné, ale za přítomnosti vedlejších účinků musí být generovány, aby byla zajištěna bezpečnost typu (více o tom viz ekvivalence typů v jazyce modulu ML ). V jazyce prvotřídních modulů vyššího řádu se tedy efektový polymorfismus ukazuje jako nezbytný základ pro podporu generativního polymorfismu : předání příznaku čistoty funktoru poskytuje volbu mezi aplikační a generativní sémantikou v jediném systému. [65]
Typově bezpečný parametrický polymorfismus je dostupný v Hindley-Milner -typovaných jazycích — v dialektech ML ( Standard ML , OCaml , Alice , F# ) a jejich potomcích ( Haskell , Clean , Idris , Mercury , Agda ) — také jako v těch, které od nich zdědily hybridní jazyky ( Scala , Nemerle ).
Generické datové typy (generika) se od parametricky polymorfních systémů liší tím, že používají omezenou kvantifikaci , a proto nemohou mít vyšší rank než 1 . Jsou k dispozici v Ada , Eiffel , Java , C# , D , Rust ; a také v Delphi od verze 2009. Poprvé se objevili v CLU .
Intenzionální polymorfismus je technika optimalizace kompilace parametrického polymorfismu založená na komplexní typově teoretické analýze, která spočívá ve výpočtech typů za běhu. Intenzionální polymorfismus umožňuje shromažďování odpadků bez značek, předávání argumentů funkcím bez rámečků a ploché datové struktury v krabicích (optimalizované pro paměť). [89] [90] [91]
Monomorfizace je technika pro optimalizaci kompilace parametrického polymorfismu, která spočívá ve generování monomorfní instance prokaždý případ použití polymorfní funkce nebo typu. Jinými slovy, parametrický polymorfismus na úrovni zdrojového kódu se převádí na ad hoc polymorfismus na úrovni cílové platformy. Monomorfizace zlepšuje výkon (přesněji řečeno činí polymorfismus „volným“), ale zároveň může zvětšit velikost výstupního strojového kódu . [92]
V klasické verzi je systém typu Hindley-Milner (také jednoduše "Hindley-Milner" nebo "X-M", anglicky HM ) [93] [94] , který je základem jazyka ML podmnožinou systému F , omezený predikativním prenex polymorfismus umožňující automatické odvozování typu , pro který Hindley-Milner tradičně zahrnoval také takzvaný „ Algorithm W “ , vyvinutý Robinem Milnerem .
Mnoho implementací X-M je vylepšenou verzí systému, která představuje „ hlavní typizační schéma “ [93] [2] , které v jediném průchodu s téměř lineární složitostí současně odvozuje nejobecnější polymorfní typy pro každý výraz a přísně kontroluje jejich dohoda .
Systém typu Hindley-Milner byl od svého vzniku rozšířen několika způsoby [95] . Jedním z nejznámějších rozšíření je podpora ad-hoc polymorfismu prostřednictvím typových tříd , které se dále zobecňují na kvalifikované typy .
Automatická inference typu byla považována za nezbytnost v původním vývoji ML jako interaktivního systému pro dokazování teorémů „ Logika pro výpočetní funkce “, a proto byla uvalena odpovídající omezení. Následně byla na základě ML vyvinuta řada efektivně kompilovaných univerzálních jazyků orientovaných na rozsáhlé programování , a v tomto případě je potřeba podpory typové inference výrazně omezena, protože modulová rozhraní v průmyslové praxi musí být v každém případě výslovně označeny typy [81] . Proto bylo navrženo mnoho Hindley-Milnerových rozšíření, která se vyhýbají inferenci typu ve prospěch zmocnění, až do a včetně podpory pro kompletní systém F s nepředvídatelným polymorfismem, jako je modulový jazyk vyššího řádu , který byl původně založen na explicitní anotace typu modulu a má mnoho rozšíření a dialektů, stejně jako rozšíření jazyka Haskell ( , a ). Rank2TypesRankNTypesImpredicativeTypes
Kompilátor MLton standardu ML monomorfizuje , ale kvůli jiným optimalizacím použitelným pro Standard ML výsledné zvýšení výstupního kódu nepřesáhne 30 % [92] .
V C nejsou funkce prvotřídními objekty , ale je možné definovat ukazatele funkcí , což umožňuje sestavit funkce vyšších řádů [96] . Nebezpečný parametrický polymorfismus je také dostupný explicitním předáním požadovaných vlastností typu přes netypizovanou podmnožinu jazyka reprezentovaného netypovým ukazatelem ).“ukazatelgenerickýnazývaný „komunitěv97][ polymorfismus ad-hoc , protože nemění reprezentaci ukazatele, je však napsán výslovně tak, aby obešel typový systém kompilátoru [96] . void* void*
Standardní funkce qsortje například schopna zpracovat pole prvků libovolného typu, pro které je definována porovnávací funkce [96] .
struct segment { int start ; int konec ; }; int seg_cmpr ( struct segment * a , struct segment * b ) { return abs ( a -> konec - a -> začátek ) - abs ( b -> konec - b -> začátek ); } int str_cmpr ( char ** a , char ** b ) { return strcmp ( * a , * b ); } struct segment segs [] = { { 2 , 5 }, { 4 , 3 }, { 9 , 3 }, { 6 , 8 } }; char * strs [] = { "tři" , "jedna" , "dva" , "pět" , "čtyři" }; hlavní () { qsort ( strs , sizeof ( strs ) / sizeof ( char * ), sizeof ( char * ), ( int ( * )( void * , void * )) str_cmpr ); qsort ( segs , sizeof ( segs ) / sizeof ( struct segment ), sizeof ( struct segment ), ( int ( * )( void * , void * )) seg_cmpr ); ... }V C je však možné idiomaticky reprodukovat typovaný parametrický polymorfismus bez použití void*[98] .
Jazyk C++ poskytuje šablonový subsystém , který vypadá jako parametrický polymorfismus, ale je sémanticky implementován kombinací ad hoc mechanismů:
šablona < název typu T > T max ( T x , T y ) { pokud ( x < y ) vrátit y ; jiný návrat x ; } int main () { int a = max ( 10 , 15 ); dvojité f = max ( 123,11 , 123,12 ); ... }Monomorfizaci při kompilaci šablon C++ se nelze vyhnout , protože typový systém jazyka postrádá podporu pro polymorfismus - polymorfní jazyk je zde statickým doplňkem k jádru monomorfního jazyka [99] . To vede k mnohonásobnému zvýšení množství přijímaného strojového kódu , což je známé jako „ code bloat “ [100] .
Zápis parametrického polymorfismu jako vývoje lambda kalkulu (nazývaného polymorfní lambda kalkul nebo Systém F ) formálně popsal logik Jean-Yves Girard [101] [102] ( 1971 ), nezávisle na něm podobný systém popsal počítačový vědec John S. Reynolds [103] ( 1974 ) [104] .
Parametrický polymorfismus byl poprvé zaveden v ML v roce 1973 [41] [105] . Nezávisle na něm byly implementovány parametrické typy pod vedením Barbary Liskov na CLU ( 1974 ) [41] .
Typy dat | |
---|---|
Neinterpretovatelné | |
Numerický | |
Text | |
Odkaz | |
Kompozitní | |
abstraktní | |
jiný | |
související témata |