Gondolkodtam még előtte h beszéljek-e majd erről, aztán végül fáradt voltam hozzá még ha maradt is volna idő... és inkább leírom akkor, mert jövő hétig lehet hogy megint tovaszáll az ihlet. :) Szóval a subtypingos ötletemet szerintem nem annyira nehéz elmondani, csak a legutóbb túlságosan a részletei végéről akartam megfogni (meg akkor is elfáradtam már a végére, meg pont a későbbi részekhez dolgoztam ki kevésbé a vázlatot előre, gondolván hogy majd úgyis leírom menetközben... stb).
A kiindulópont ill. motivációm hozzá ugye két dologból áll: (a) nem akarok automatikus meet-eket meg join-okat (if blabla { kutya } else { macská }-ból "állat"-ot inferálni), mert ez conflictol a statikus ad-hoc polimorfizmussal / típus-alapú rezolúcióval (mint a type classok, vagy C++ban az overloading), hanem csak 'egyeneságú' upcast-eket akarok támogatni; meg (b) a saját use case-emhez nem tetszik az mlsub algoritmusnak a skálázása (minden új consumernél végig kell menni minden meglévő produceren és vice-versa, és mindkettő idővel egyre csak gyarapodik). Ezeket a motivációkat lehet vitatni (meg nem kizárt hogy én is meggondolom akár magamat valamikor:), de ahhoz a kontextusról is többet kellene mondanom, úgyhogy ebben a scope-ban most egyelőre vegyük ezeket adottnak.
Az alapötlet annyiból áll, hogy minden metaváltozónak két oldala van: hogy mi az egyedi típus, amiképp ez a metaváltozó előállítódik, ill. mi az egyedi típus, amiként fel van használva; és ez a kettő külön-külön oldódik meg / állítódik be. És értelemszerűen a producer oldalnak subtypejának kell majd lennie a consumernek (ami triviálisan akkor is teljesül, amikor a kettő megegyezik). Az oldalankénti 1db egyedi típus megszorítás mögött az a logika, hogy ha a metaváltozó több, különböző típusból is előállna, akkor az azt jelentené, hogy ott impliciten a join-jukat vennénk, és hasonlóképp a másik oldalon a többféle típusként való felhasználás a meet-et jelentené.
Ha vesszük azt a függvényt, amit
a múltkori pszeudokódban "requireEquals", "requireSubtype", ill. "unify"-nak neveztem kontextustól függően - amit a typechecker akkor hív meg, amikor tudja, hogy mi a provided type meg mi az expected type, és validálni akarja, hogy ezek kompatibilisek - akkor ez itt négy alapvető esetre bomlik:
- Ha mindkét oldalon konkrét típus áll (nem metaváltozó), akkor semmi extra, ugyanúgy történik az upcastelés, mint a sima bidirectional typechecker esetén.
- Ha a provided type metaváltozó, és az expected type konkrét típus, akkor az előbbinek a "consumer" oldalát beállítjuk az utóbbira.
- És fordítva, ha a provided type konkrét típus és az expected type metaváltozó, akkor az utóbbinak a "producer" oldalát állítjuk be az előbbire.
- Ha mindkettő metaváltozó, akkor egyszerűen megoldjuk az egyiket a másikra tetszőleges irányban (és mindkét oldalukat beleértve), ugyanúgy mint a "sima" unification esetében is történne.
A második és harmadik esetnél, ha a "túloldali" típus ezen a ponton már be van állítva (most állítjuk be a producert, és már ott van a consumer is, vagy fordítva), akkor ezen a ponton csináljuk meg a subtyping ellenőrzést ill. "upcastelést" a két típus között. Ha pedig be van már állítva az az oldal, amit éppen mi is beállítanánk, akkor a két típust (a meglévő ill. "új" producert, vagy a meglévő és új consumert) egyenlővé tesszük: lényegében egy hagyományos unificationt hajtunk végre rajtuk, subtypingos aszimmetria nélkül. (Mert ugyebár csak 1db egyedi producer, ill. consumer típus lehet.)
A negyedik esetnél, ha a metaváltozók egyes oldalai be vannak már állítva, akkor hasonlóképp szimmetrikus/hagyományos módon unifikáljuk a két producer oldali típust egymással, ill. a két consumer oldali típust egymással.
A negyedik esetnek az indoklása az érdekes: hogy miért "szabad" itt közvetlenül egymásra megoldanunk (egyenlővé tennünk) a két metaváltozót, annak ellenére, hogy ez egy "aszimmetrikus" (producer-consumer) helyzet. Ez is abból fakad, hogy az adott metaváltozónak a producer ill. consumer oldali típusának egyedinek kell lennie. Logikailag ebben a helyzetben az "történik", hogy az egyik metaváltozó fel van használva a másikként, ill. a másik elő van állítva az egyikként: másképp fogalmazva, az derül ki, hogy az "expected type" metaváltozó az a "provided type" metaváltozónak a producere által fog előállítódni, tehát az előbbinek is az a producere, ami az utóbbié; és fordítva, a "provided type" metaváltozó az az "expected type" metaváltozónak a consumere által fog végül felhasználódni, így az előbbinek is az a consumere, ami az utóbbié. És mivel producerből meg consumerből is csak 1 lehet, ez meg is határozza, hogy a két producer oldal ugyanaz, meg a két consumer oldal is ugyanaz, tehát a két metaváltozó egy az egyben egyenlő.
Intuitíven itt valami olyasmi valósul meg ezáltal, hogy ha van egy felhasználási láncolat, amin keresztül egy érték eljut A pontból B-be, akkor mindegy, hogy hány metaváltozó van a kettő között, a két végpont között létre tud jönni az upcast. (A fejemben olyasmi képpel, hogy az upcast teleszkópos kézzel keresztülnyúl a kettő közötti távolságon, mint a rajzfilmekben...)
Ennyit akartam leírni róla. Minden kérdésnek örülök, hogy hol homályos még, vagy mi hiányzik még hozzá. :)
(Van még pár további érdekes kérdés is ezekből továbbindulva, de azokba csak akkor megyek bele, ha kiderül, hogy ez eddig nem volt teljesen érthetetlen, ill. valakit érdekel is; vagy az is lehet, hogy azokról esetleg a következő meetingen akkor.)