Portál AbcLinuxu, 30. dubna 2025 20:48
Takže pro vyhledávání slotů používáš DFS. Uvažoval jsi o plném prohledávání tak, jak to dělá Self?
Jsem zvědav, jak budeš řešit kontexty a procesy
Uvažoval jsi o plném prohledávání tak, jak to dělá Self?Mám na to issue, ale ještě jsem se k tomu nedostal. Četl jsem někde v paperu od Davida Ungara, že bez toho to není úplně korektní.
Jsem zvědav, jak budeš řešit kontexty a procesyProcesy už mám +- vyřešené. Co je konkrétně myšleno kontexty? Lokální scope?
Četl jsem někde v paperu od Davida Ungara, že bez toho to není úplně korektní.
U složitějších systémů se bez toho dá hůře odhadnout, jak se bude program vlastně skutečně chovat, ale na druhou stranu přináší DFS větší flexibilitu.
Co je konkrétně myšleno kontexty? Lokální scope?
Myslel jsem obecně přístup ke stacku a případnou manipulaci s ním
Myslel jsem obecně přístup ke stacku a případnou manipulaci s nímTo bude vidět v příštím díle. Já si to dělám dost po svém a taky je to z jisté míry ovlivněné pythonem, ve kterém to píšu, tak se to bude asi lišit poměrně hodně od toho co je v Selfu. Momentálně řeším nejvíc pomalost, kde už jsem zoptimalizoval kde co, a dostal jsem se někam k 3 vteřinám na 1 milion while cyklů, což je pořád hrozně pomalé. Teď se snažím ve vedlejší branchi implementovat cacheování slotů a dynamickou rekompilaci bytecode, kde podle profileru parent lookupy zabírají víc jak 70% výkonu.
Uvažoval jsi o plném prohledávání tak, jak to dělá Self?Ze zvědavosti: Jak to dělá Self? Nedělá DFS?
Když Self prohledává sloty v rodičích, prohledá úplně všechny možnosti a pokud hledanému slotu odpovídá více možností, vyhodí chybu. Jediná validní možnost, jak překrýt definici nějakého rodičovského slotu, je udělat to v rámci vlastních slotů objektu, nemůžete k tomu použít jiného rodiče.Jen dodám, že pak je nutné použít resend (směrování do konkrétního parenta), což je korektní řešení.
Když Self prohledává sloty v rodičích, prohledá úplně všechny možnosti a pokud hledanému slotu odpovídá více možností, vyhodí chybu. Jediná validní možnost, jak překrýt definici nějakého rodičovského slotu, je udělat to v rámci vlastních slotů objektu, nemůžete k tomu použít jiného rodiče.Aha, rozumim. To připomíná řešení ambiguit v multiple inheritance kde např. jazyky jako C++ taky vyhazují chyby v podobných situacích, zatímco například v Go tohle přehlédli a jsou z toho pak problémy.
Uvažoval jsi o plném prohledávání tak, jak to dělá Self?Nedávno jsem to implementoval btw.
Jednou z naprosto klíčových funkcionalit Selfu, která mu dodává mnoho elegance, jenž například postrádá javascript, je delegace na rodičovské sloty.Chápu to správně, že ten rozdíl oproti JavaScriptu je v tom, že v JS může mít objekt pouze jednoho rodiče?
Object obsahuje kromě metodyChvíli mi dalo tuhle větu rozparsovat, jestli jsem to pochopil správně, chtělo by to čárku mezi 'slot' a 'také'..get_slot()
, jenž pouze hledá v mapě patřičný slot také metody.parent_lookup()
a.slot_lookup()
.
Chápu to správně, že ten rozdíl oproti JavaScriptu je v tom, že v JS může mít objekt pouze jednoho rodiče?Spíš že do javascriptu je to tak divně naprasené, že se to reálně stejně moc nepoužívá, kdežto v Selfu je to doslova základ a staví na tom prakticky každý objekt, včetně třeba právě implementace lokálního scope. Podle mě to není jen kulturou, ale i podporou v syntaxi. Ono obecně, prototypy v javascriptu tě tlačí k tomu přát si aby to byl class based systém. Prototypy v Selfu oproti tomu působí přirozeně a konzistentně.
Chvíli mi dalo tuhle větu rozparsovat, jestli jsem to pochopil správně, chtělo by to čárku mezi 'slot' a 'také'.Máš pravdu, opravím to.
Spíš že do javascriptu je to tak divně naprasené, že se to reálně stejně moc nepoužívá, kdežto v Selfu je to doslova základ a staví na tom prakticky každý objekt, včetně třeba právě implementace lokálního scope. Podle mě to není jen kulturou, ale i podporou v syntaxi. Ono obecně, prototypy v javascriptu tě tlačí k tomu přát si aby to byl class based systém. Prototypy v Selfu oproti tomu působí přirozeně a konzistentně.Afaik v JS je to dané mimo jiné také optimalizacemi, viz tady a tady.
[hidden]
u šipek, kde můžeš konečně silou donutit layout vykreslit se jak chceš. To mi dřív vždycky trvalo hrozně dlouho.
Začal jsem na to teď psát takový python wrapper, kde jen skládáš třídy do sebe a pak ti z toho krásně vypadne UML graf, ale zatím je to jen takové experimentování. Je to ale super právě na vizualizace třeba těch scope chainů.
BTW: když je řeč o diagramech, znáte někdo Adaptagramy a Dunnart? Přijde mi to jako (takřka) ideální kompromis mezi ručním kreslením grafu a automatickým generováním z nějakého předpisu – základem je vlastně předpis podle kterého lze vygenerovat graf, ale člověk k tomu může přidat různá omezení, jako tenhle uzel má být přesně tady nebo mezi těmito uzly mají být stejné mezery nebo tyhle uzly mají být na vodorovné přímce.
Funguje tam přetěžování 1 metod? Když je to implementované jako slovník, tak asi ne. Nebo je klíčem něco jiného než název metody?
Jak se to bude chovat v případě, že zavolám metodu na neexistujícím objektu (None
)? Vyletí něco jako NullPointerException
nebo to budeš řešit nějakým lepším způsobem? Když jsem nad tím někdy přemýšlel – tedy v kontextu třídní dědičnosti – tak jsem si říkal, že by třída mohla definovat výchozí chování metod pro případ, že je zavoláš na null
objektu. Pak by šlo implementovat chování třeba tak, že když se pokusíš iterovat přes chybějící (null
) seznam, chovalo by se to stejně, jako kdybys iteroval přes prázdný seznam. U té prototypové dědičnosti víš, jakého typu mělo být None
, nebo ne?
[1] více metod se stejným názvem a různými parametry (ne překrývání)
Funguje tam přetěžování 1 metod? Když je to implementované jako slovník, tak asi ne. Nebo je klíčem něco jiného než název metody?Ono je to hlavně dynamicky typované, takže víc metod se stejnými názvy nedává smysl, nemáš je podle čeho rozlišit. Metody tam navíc nejsou, jsou to zprávy a počítá se pouze název zprávy.
Jak se to bude chovat v případě, že zavolám metodu na neexistujícím objektu (None)?Přemýšlím jak by k tomu mohlo dojít a nic mě nenapadá. Nic jako neexistující objekt tam není. Interně se
None
sice používá, ale to je na úrovni implementace, ne na úrovni něčeho k čemu bys mohl přistupovat z tinySelfu samotného. Je tam nil, ale to je prostě jen singleton normálního objektu, takže se na to reaguje jako když pošleš zprávu libovolnému jinému objektu (momentálně to mám nastavené tak že to spadne a vypíše debug, ale správně by to mělo fungovat tak že se dá objektu šance reagovat (vyhledání metody pro dynamický resolve) a pokud se nenajde, zavolá error handler).
Pak by šlo implementovat chování třeba tak, že když se pokusíš iterovat přes chybějící (null) seznam, chovalo by se to stejně, jako kdybys iteroval přes prázdný seznam.Tam nikdy nic jako null seznam nebude.
momentálně to mám nastavené tak že to spadne a vypíše debugCož je vlastně ten
NullPointerException
.
ale správně by to mělo fungovat tak že se dá objektu šance reagovat (vyhledání metody pro dynamický resolve) a pokud se nenajde, zavolá error handler).To už odpovídá tomu mému nápadu. Akorát u té třídní dědičnosti je výhoda v tom, že víš, jakého typu ten
null
měl být, takže víš, ve které třídě hledat. V dynamickém systému tahle informace chybí, takže to asi spadne do nějakého obecného objektu, předka všech předků, a v něm se to nějak genericky zpracuje. Šlo by z toho vytáhnout aspoň ten kontext/objekt, ve kterém se ten chybějící slot nacházel?
Tam nikdy nic jako null seznam nebude.Jak se to tedy bude chovat v případě, že na nějakém místě očekávám seznam, ale on tam není a místo toho to ukazuje na
nil
objekt? To je přece v principu stejné, jako kdybych měl proměnnou, ve které čekám ArrayList
, ale bylo v ní null
, ne?
To už odpovídá tomu mému nápadu. Akorát u té třídní dědičnosti je výhoda v tom, že víš, jakého typu ten null měl být, takže víš, ve které třídě hledat. V dynamickém systému tahle informace chybí, takže to asi spadne do nějakého obecného objektu, předka všech předků, a v něm se to nějak genericky zpracuje. Šlo by z toho vytáhnout aspoň ten kontext/objekt, ve kterém se ten chybějící slot nacházel?Momentálně je to jen ve fázi návrhu, takže jak si to udělám, takové to bude. Teď řeším hlavně rychlostní optimalizace, s tím že cílem je se dostat s milionem while cyklů pod jednu vteřinu (while cyklus v každé iteraci testuje a vytváří dynamickou lambda funkci), a ideálně pod 100ms. Jsem někde okolo tří vteřin bez jitu.
Jak se to tedy bude chovat v případě, že na nějakém místě očekávám seznam, ale on tam není a místo toho to ukazuje na nil objekt? To je přece v principu stejné, jako kdybych měl proměnnou, ve které čekám ArrayList, ale bylo v ní null, ne?Tak jako vždycky v ducktypovaných jazycích (třeba pythonu), padne to na tom že se objektu pošle zpráva které nerozumí. Momentálně by se to prostě ukončilo, ale to je čistě jen řešení ve stylu „v téhle fázi vývoje mě to nezajímá“. V budoucnosti v tu chvíli dojde k zavolání error handleru na dané úrovni stackframe, což pokud není změněno kódem, tak povede ke spuštění interaktivního debuggeru. Ten error handler už tam mám teď, akorát se prostě nevolá u chybějícího slotu, protože se mi teď nechtělo. Trochu koketuji s myšlenkou, že bych dovolil metodám definovat rozhraní parametrů a výstupní hodnoty, a přidal k tomu nějaké statické typování, ale není to v TODO, jen ve fázi „hm, to by nemuselo být špatné“. Funkcionalita momentálně z větší části klonuje Self.
Když jsem nad tím někdy přemýšlel – tedy v kontextu třídní dědičnosti – tak jsem si říkal, že by třída mohla definovat výchozí chování metod pro případ, že je zavoláš na null objektu. Pak by šlo implementovat chování třeba tak, že když se pokusíš iterovat přes chybějící (null
) seznam, chovalo by se to stejně, jako kdybys iteroval přes prázdný seznam.
Tohle řeší funcionální a jimi ovlivněné jazyky typovým systémem a použitím ADTs.
Tady máš příklad v Rustu. (Ta metoda get_list()
náhodně vrací "nic" nebo vektor čísel.)
Přijde mi to lepší mj. v tom, že to není závislé na objektech / dědičnosti a můžeš se rozhodnout, co dělat s prázdnou hodnotou, jak zrovna potřebuješ, není to zadrátováno v té třídě.
Z hlediska zápisu a pohodlnosti se to:
let list = get_list().unwrap_or(vec![]);
ale moc neliší od:
list = list == null ? Collections.emptyList() : list;
(mimochodem v Javě někdy používám svoje notNull())
To Option.unwrap_or()
tě akorát donutí se k té null
hodnotě nějak postavit a nezapomenout na její ošetření. Ale tu práci a rozhodování musíš udělat tak jako tak. (a pokud nezapomínáš nebo ten kód testuješ, tak v tom opravdu moc velký rozdíl není)
Mně šlo spíš o to mít možnost, se téhle práce úplně zbavit, resp. přesunout to rozhodování někam jinam, aby nebylo rozeseté všude možně v kódu. Přišlo by mi fajn mít možnost na jednom místě definovat, jak se chovat k chybějícím hodnotám různých typů.
Přijde mi, že někdy by se to hodilo mít definované tohle výchozí chování na úrovni tříd. Na druhou stranu je pravda, že by to šlo řešit i tím, že null
prostě nikde nebude a místo něj budeme mít jen nějaké singletony, které tam dáme místo něj a budou jednak symbolizovat, že jde o prázdnou hodnotu (akorát místo if(a == null)
se bude psát if (a == Někde.nějaký.SINGLETON)
nebo if (isMissingValue(a))
).
Např. když budu mít atribut, který definuje nějaké filtrovací pravidlo, tak null
by se interpretoval tak, že se nic filtrovat nemá a projde vše. Ale taky by šlo vynutit (např. nějakou anotací nebo klíčovým slovem), že ten atribut null
nikdy nebude – a když v něm budu chtít mít prázdný filtr, tak tam dám singleton, který bude na všechno odpovídat true
, tudíž projde vše.
Přišlo by mi fajn mít možnost na jednom místě definovat, jak se chovat k chybějícím hodnotám různých typů.To se dela pres monady. Option je monada, takze muzes skladat funkce co s ni vselijak pracuji aniz bys musel resit, jak presne se chova.
Přijde mi, že někdy by se to hodilo mít definované tohle výchozí chování na úrovni tříd.Tak v tom Rustu se tohle nestane, protože tam žádný
null
není a každý objekt je vždy platný. Ten Option
je jen obyčejný enum a None
je jen varianta tohoto enumu, nemá žádný specielní význam pro kompilátor. Takže v zásadě ti nic nebrání si nadefinovat vlastní typ, který bude nějakým jiným způsobem reprezentovat prázdnou hodnotu, třeba pomocí Default
, jako např. takhle, to by asi zhruba byl rustový ekvivalent toho, co chceš, ačkoli obecně se to nepoužívá, protože to není potřeba, viz dále...
Ale taky by šlo vynutit (např. nějakou anotací nebo klíčovým slovem), že ten atribut null nikdy nebudeAno, pokud vyloženě nemáš null-free jazyk, tak můžeš třeba udělat to, co dělá Kotlin. Náhradou "null" je pak konkrétní stav nějakého objektu, například u toho vektoru prostě vrátíš prázdný vektor a s null se nezabýváš, protože to nemá v té chvíli smysl - proč přidávat další možný stav objektu (
null
) jen proto, abys ho následně natvrdo namapoval na již existující stav (prázdný vektor)?
třeba pomocí Default, jako např. takhle, to by asi zhruba byl rustový ekvivalent toho, co chcešDík, vypadá to zajímavě.
Náhradou "null" je pak konkrétní stav nějakého objektu, například u toho vektoru prostě vrátíš prázdný vektor a s null se nezabýváš, protože to nemá v té chvíli smysl - proč přidávat další možný stav objektu (null) jen proto, abys ho následně natvrdo namapoval na již existující stav (prázdný vektor)?Jo, přesně.
null
na sémanticky ekvivalentní instanci cílového typu by mohla být fajn. Mně osobně se hodně líbí nullable typy v C# (zjednodušeně: proměnná typu Foo?
může být nastavena na null
, proměnná typu Foo
nikoliv). C# obecně je Java na steroidech, alespoň co se core jazyka týká.
Však Java má Optional, což je potom 1:1 jako ta ukázka z Rustu.Když zamhouřím oči tak jo. Když nezamhouřím, tak si budu stěžovat minimálně na to, že ten
Optional
může sám být null
, takže odpovídá spíš něčemu jako Option<Option<T>>
...
Mně osobně se hodně líbí nullable typy v C# (zjednodušeně: proměnná typuStejně to má i Kotlin, asi to převzali z C#...Foo?
může být nastavena nanull
, proměnná typuFoo
nikoliv). C# obecně je Java na steroidech, alespoň co se core jazyka týká.
Když zamhouřím oči tak jo. Když nezamhouřím, tak si budu stěžovat minimálně na to, že tenPravda, přehlédl jsem, že ty v tom Rustu vracíšOptional
může sám býtnull
, takže odpovídá spíš něčemu jakoOption<Option<T>>
...
None
a ta konverze na Option
proběhne implicitně. Nicméně já bych stav, kdy samotný Optional
bude null
, stejně neošetřoval, přijde mi to hloupé. IMHO nemá smysl psát každou metodu bullet-proof tak, aby nebylo možné ji rozbít. V ideálním případě by jazyk vůbec neumožnil tam ten null
poslat, když to neumí, bude to padat za běhu a ne v čase kompilace. To statické typování v Javě holt má svoje problémy.
Pravda, přehlédl jsem, že ty v tom Rustu vracíšNo, ona to není konverze.None
a ta konverze naOption
proběhne implicitně.
None
je jedna z variant enumu Option
, tj. reálně ta funkce vrací Option::None
. V Rustu může člověk importovat do scope nejen ten enum (tj. typ), ale i jeho varianty. A ten Option
jakož i jeho varianty jsou by-default importovány, prostě protože se to používá často. Proto je možný prostě vrátit None
.
Nicméně já bych stav, kdy samotnýJo, s tim souhlasim, je to trochu hnidopišská připomínka. Afaik mají v plánu to fixnout přidáním podpory value types, ale nevim, jak je to daleko...Optional
budenull
, stejně neošetřoval, přijde mi to hloupé. IMHO nemá smysl psát každou metodu bullet-proof tak, aby nebylo možné ji rozbít. V ideálním případě by jazyk vůbec neumožnil tam tennull
poslat, když to neumí, bude to padat za běhu a ne v čase kompilace.
Tiskni
Sdílej:
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.