Ideálny programovací jazyk

Re:Ideálny programovací jazyk
« Odpověď #60 kdy: 09. 05. 2019, 22:13:23 »
To jsem ted uplne asi nepochopil, v C bych mel design takovy, ze polymorfismus budu delat jen  nad funkcemi, nikoliv nad strukturami. Nad strukturami navic asi uplne polymorfismus delat nepotrebuju.
Funkce k sobě obvykle potřebujou nějaké specifické data. Situace, že by všem polymorfním funkcím stačila stejná data mi přijde poměrně vzácná. Všechny rozumné callbacky, které berou nějaký pointer na funkci berou i nějaký void pointer na data. Cpát jim je v globálách sice jde, ale není to ono. Jinak C++ kód
Kód: [Vybrat]
class Base
{
public:
  virtual void foo();
  int a;
};
class Derived : public Base
{
  void foo() override;
  int b;
};
odpovídá C kódu :
Kód: [Vybrat]
struct Base
{
  struct Vft *vft;
  int a;
};
struct Derived
{
  struct Base base;
  int b;
};
struct Vft
{
  void (*foo)( Base *this );
};
+ 2 konstaty typu Vft s ukazateli na dvě verze funkce foo.
C++ k tomuhle dělá v podstatě jen syntaktický cukr, takže samo nastavuje pointery na Vft, automaticky konvertuje typy pointerů a zjednodušuje volání metod. Třeba v Cčkových hlavičkách pro windowsí COM objekty se dá najít, jak vypadá volání virtuálních funkcí C++ z C kódu. Implementace objektů v C vypadá obvykle velice podobně, jen se občas vypouští ta vft a pointery na funkce se cpou přímo do Base, pokud jich není moc. Kód, který dostane pointer na Base pak nemůže vědět, jestli je to Base, nebo začátek Derived. A pokud by tu Base zkopíroval, tak udělá slicing jako C++ (jen s drobným rozdílem, že v C++ se nebude slepě kopírovat ten pointer na vft).

Citace
Mimochodem jde v C udelat to, ze 1 hlavicka bude pro 2 ruzne implementace? Ze by pak hlavicka hrala roli interfacu (neplest prosim s OOP) k vicero implementacim?
Samozřejmě. Záleží jenom na tom, jaké obj soubory dostane linker. Nebo jaká verze .dll/.so se předhodí programu při startu. V C se funkce linkují čistě podle jména. Linker dokonce ani nemůže zkontrolovat typy, protože C na rozdíl od C++ nedělá name mangling.


Re:Ideálny programovací jazyk
« Odpověď #61 kdy: 09. 05. 2019, 23:16:34 »


Dekuji za priklad. Presto si myslim, ze mi zde neco v C paradigmatu unika. Ono neni vzdycky nutne ani ten polymorfismus pouzivat. Mam treba ted komponentu o 300k radku kodu v Jave, kde polymorfismus a generika se pouziva velice vzacne - v podstate hlavne v testech pri mockovani. A taky to jde a je to prehledne, ne-li prehlednejsi, protoze tam nejsou zaludnosti.

Myslim si, ze k programovani se da pristupovat i jinak, ale jsem zvykly na Javu a OOP paradigma a ono to na to C-paradigma napasovat nejde, ale dost mozna ani to jit nemusi.

Me se moc OOP programovani v C nelibi, ikdyz to jde. Ale profi zkusenost s C nemam. Ze zvyku hned vyhledavam jak udelat polymorfismus, ikdyz to treba vlastne ani neni nutne a da se to vymyslet jinak.

Ono kolikrat jazyk obsahuje nejruznejsi funkcionality, treba C#, a clovek pak muze nabyt klameho dojmu ze bez nich to nejde a dokonce je pouziva zbytecne a spatne, jako napr. psat vsechno async, vsude se snazit napasovat generikum, vsude se snazit strcit yield, vsude pouzivat "?" misto toho aby se clovek zamyslel proc musi porad checkovat neco na null, jestli neni  nekde chyba v designu.

C umi vsechno co je k napsani aplikace potreba, mozna jestli neni nekdy chyba mezi klavesnici a zidli :-) Ja vlastne ani nevim, jak se ma v C spravne aplikace psat, pcinaje VS delam jen v OOP jazycich.
« Poslední změna: 09. 05. 2019, 23:18:24 od PetrK »

Re:Ideálny programovací jazyk
« Odpověď #62 kdy: 10. 05. 2019, 08:02:36 »
Jenom tak na okraj: v Go se slicing dělá i s pointery, je to jedna z věcí, které mě na Go fakt štvou. Při volání se prostě předá ukazatel opravdu jenom na ten typ, ktery v hlavičce je (tj. jenom embedded struct Base). Je to sice konzistentnější při jednom použití (volání odkazem i hodnotou se chová stejně), ale zase je nekonzistentní, že když tu samou funkci volám přímo na structu a nebo až uvnitř funkce, kam jsem struct předal, výsledek je jiný (tím voláním funkce se "vyřízne" jenom embedded struct):

https://play.golang.org/p/MCmwySyPQpl

Re:Ideálny programovací jazyk
« Odpověď #63 kdy: 10. 05. 2019, 08:16:16 »
Aby volající kód mohl kopírovat neznámý odvozený typ, musel by mít k dispozici něco jako "virtuální kopírovací konstruktor" + podporu objektů neznámého typu na zásobníku.
To by bylo možná lepší řešení - na způsob Copy traitu v Rustu.

Co si mám představit pod "podporu objektů neznámého typu na zásobníku"? Funkce s těmi daty nic nemusí dělat, musí je umět jenom zkopírovat a vědět, kde začínají data, kterým rozumí. Ostatní může v klidu ignorovat, ne?

Idris

  • *****
  • 2 286
    • Zobrazit profil
    • E-mail
Re:Ideálny programovací jazyk
« Odpověď #64 kdy: 10. 05. 2019, 08:43:55 »
Jenom tak na okraj: v Go se slicing dělá i s pointery, je to jedna z věcí, které mě na Go fakt štvou. Při volání se prostě předá ukazatel opravdu jenom na ten typ, ktery v hlavičce je (tj. jenom embedded struct Base). Je to sice konzistentnější při jednom použití (volání odkazem i hodnotou se chová stejně), ale zase je nekonzistentní, že když tu samou funkci volám přímo na structu a nebo až uvnitř funkce, kam jsem struct předal, výsledek je jiný (tím voláním funkce se "vyřízne" jenom embedded struct):

https://play.golang.org/p/MCmwySyPQpl
To je docela konzistentní chování. Navíc ten kód není zrovna idiomatický. Jedním slovem: non-issue.


Re:Ideálny programovací jazyk
« Odpověď #65 kdy: 10. 05. 2019, 08:50:46 »
Navíc ten kód není zrovna idiomatický.
Ten kód jenom ukazuje, co se tam děje. Jinak to ukázat najde (AFAIK), takže tuhle výhradu neberu.

Jedním slovem: non-issue.
Může to být issue pro lidi, kteří mají subtypový polymorfismus pod kůží, chtějí nadesignovat kód tak, jak jsou zvyklí (předám někam Derived jako Base, něco se s tím děje, pak to dostanu zpátky a přetypuju si zase na Derived), a ejhle: ono to v Go takhle nejde :)

Jasně, správný Go řešení je použít interfaces, o tom se nehádám, jenom říkám, že to může být pro někoho docela překvapení. Mně osobně tenhle "supersilnej nominalismus" ("objekt" se chová ne podle toho čím je, ale podle toho, jaký typ je v kódu u něj napsaný) mně osobně moc nevyhovuje.

Idris

  • *****
  • 2 286
    • Zobrazit profil
    • E-mail
Re:Ideálny programovací jazyk
« Odpověď #66 kdy: 10. 05. 2019, 09:29:28 »
Navíc ten kód není zrovna idiomatický.
Ten kód jenom ukazuje, co se tam děje. Jinak to ukázat najde (AFAIK), takže tuhle výhradu neberu.

Jedním slovem: non-issue.
Může to být issue pro lidi, kteří mají subtypový polymorfismus pod kůží, chtějí nadesignovat kód tak, jak jsou zvyklí (předám někam Derived jako Base, něco se s tím děje, pak to dostanu zpátky a přetypuju si zase na Derived), a ejhle: ono to v Go takhle nejde :)

Jasně, správný Go řešení je použít interfaces, o tom se nehádám, jenom říkám, že to může být pro někoho docela překvapení. Mně osobně tenhle "supersilnej nominalismus" ("objekt" se chová ne podle toho čím je, ale podle toho, jaký typ je v kódu u něj napsaný) mně osobně moc nevyhovuje.
Když takhle napíšu Base, jde o kompozici. Jak jinak by to asi mělo fungovat?

Re:Ideálny programovací jazyk
« Odpověď #67 kdy: 10. 05. 2019, 09:37:07 »
Když takhle napíšu Base, jde o kompozici. Jak jinak by to asi mělo fungovat?
Problém je v tom, že se to na první pohled tváří jako subtypový polymorfismus (Derived můžu předat do funkce, která očekává Base), ale ve skutečnosti to tak není. Protože to může běžného frantu programátora zmást, možná by neškodilo víc explicitnosti:

Kód: [Vybrat]
func f(b Base) {
 ...
}

...

d := Derived{...}

// ok
f(d.Base)
// err
f(d)
- pokud by to bylo takhle, bylo by na první pohled jasné, že do f předávám jenom část d.

...ale nechci autorům Go radit, chtěl jsem jenom říct, že taková nepříjemná zákoutí nejsou jenom v C++, ale bohužel i v Go, které jinak lidi mají za dost čistý a konzistentní jazyk.

Idris

  • *****
  • 2 286
    • Zobrazit profil
    • E-mail
Re:Ideálny programovací jazyk
« Odpověď #68 kdy: 10. 05. 2019, 09:42:51 »
Když takhle napíšu Base, jde o kompozici. Jak jinak by to asi mělo fungovat?
Problém je v tom, že se to na první pohled tváří jako subtypový polymorfismus (Derived můžu předat do funkce, která očekává Base), ale ve skutečnosti to tak není. Protože to může běžného frantu programátora zmást, možná by neškodilo víc explicitnosti:

Kód: [Vybrat]
func f(b Base) {
 ...
}

...

d := Derived{...}

// ok
f(d.Base)
// err
f(d)
- pokud by to bylo takhle, bylo by na první pohled jasné, že do f předávám jenom část d.

...ale nechci autorům Go radit, chtěl jsem jenom říct, že taková nepříjemná zákoutí nejsou jenom v C++, ale bohužel i v Go, které jinak lidi mají za dost čistý a konzistentní jazyk.
Go zrovna moc konzistentní není. Ale ta implicitní kompozice dává smysl. Že si někdo myslí, že Go má podtypový polymorfismus, to už je jiná věc, taky nebudu kritizovat Porsche Carrera, že s ním nemůžu orat pole, na základě toho, že jsem si myslel, že to jde.

Re:Ideálny programovací jazyk
« Odpověď #69 kdy: 10. 05. 2019, 09:49:46 »
taky nebudu kritizovat Porsche Carrera, že s ním nemůžu orat pole, na základě toho, že jsem si myslel, že to jde.
Janže ono to jde a není to žádná obezlička, je to jasná součást jazyka - Derived můžu předat do funkce, která očekává Base. Nepředávám d.Base, předávám d. Je to nejspíš jediná situace, kde se v Go může předávat jiný typ než očekávaný.

A protože to není intuitivní, vyžaduje to explicitní vysvětlení:
Citace
There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one. In our example, when the Read method of a bufio.ReadWriter is invoked, it has exactly the same effect as the forwarding method written out above; the receiver is the reader field of the ReadWriter, not the ReadWriter itself.
https://golang.org/doc/effective_go.html#embedding

Není to ani čisté skládání (jako v C), ani subtyping. Je to takové divné "něco mezi" s vlastnostmi, které si člověk musí pamatovat, protože nikde jinde to tak nefunguje (naštěstí jich není moc).

Idris

  • *****
  • 2 286
    • Zobrazit profil
    • E-mail
Re:Ideálny programovací jazyk
« Odpověď #70 kdy: 10. 05. 2019, 10:00:52 »
taky nebudu kritizovat Porsche Carrera, že s ním nemůžu orat pole, na základě toho, že jsem si myslel, že to jde.
Janže ono to jde a není to žádná obezlička, je to jasná součást jazyka - Derived můžu předat do funkce, která očekává Base. Nepředávám d.Base, předávám d. Je to nejspíš jediná situace, kde se v Go může předávat jiný typ než očekávaný.

A protože to není intuitivní, vyžaduje to explicitní vysvětlení:
Citace
There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one. In our example, when the Read method of a bufio.ReadWriter is invoked, it has exactly the same effect as the forwarding method written out above; the receiver is the reader field of the ReadWriter, not the ReadWriter itself.
https://golang.org/doc/effective_go.html#embedding

Není to ani čisté skládání (jako v C), ani subtyping. Je to takové divné "něco mezi" s vlastnostmi, které si člověk musí pamatovat, protože nikde jinde to tak nefunguje (naštěstí jich není moc).
Jo, je nutné si to pamatovat. Toho je v Go víc. Pak je otázka, jestli se taky člověk bez takovéhoto embeddingu obejde.

Re:Ideálny programovací jazyk
« Odpověď #71 kdy: 10. 05. 2019, 10:06:41 »
Pak je otázka, jestli se taky člověk bez takovéhoto embeddingu obejde.
Jasně, to je legitimní otázka. A odpověď jsem už naťukl: v C se bez něj obejdeš, protože Base je skutečně explicitně embeddovaný - je to prostě field v Derived. Čili podle mě je to v Go čistě syntaktický cukr, kdy místo d.Base napíšeš d a překladač to pochopí. Z toho vyvozuju, že všechno by mohlo zůstat jak je, jenom bys musel psát d.Base. Neumím si představit cokoli, o co by tím jazyk přišel.


Re:Ideálny programovací jazyk
« Odpověď #72 kdy: 10. 05. 2019, 10:22:05 »
Co si mám představit pod "podporu objektů neznámého typu na zásobníku"? Funkce s těmi daty nic nemusí dělat, musí je umět jenom zkopírovat a vědět, kde začínají data, kterým rozumí. Ostatní může v klidu ignorovat, ne?
Měl jsem na mysli hlavně velikost známou až v runtimu. Jedna věc jsou dočasné proměnné. Ty jsou v pohodě, i když najednou nutně potřebujeme registr pro frame pointer. Druhá věc je ale vracení objektu neznámé velikosti hodnotou. Volající neví kolik paměti vyhradit a volaný to taky nezvládne alokovat bez větších problémů. Musel by ten zásobník přeskládat. Neznám žádné volací konvence, které by něco takového uměly.

Re:Ideálny programovací jazyk
« Odpověď #73 kdy: 10. 05. 2019, 10:30:27 »
Měl jsem na mysli hlavně velikost známou až v runtimu. Jedna věc jsou dočasné proměnné. Ty jsou v pohodě, i když najednou nutně potřebujeme registr pro frame pointer. Druhá věc je ale vracení objektu neznámé velikosti hodnotou. Volající neví kolik paměti vyhradit a volaný to taky nezvládne alokovat bez větších problémů. Musel by ten zásobník přeskládat. Neznám žádné volací konvence, které by něco takového uměly.
Já ty lowlevel detaily dobře neznám a C++ jsem viděl naposledy tak před deseti lety, takže mi asi spousta věcí uniká, ale čistě od stolu mě překvapuje, že je to problém. Přijde mi, že by stačily dvě čísla na začátku: velikost Derived a offset Base v rámci Derived. Pokud potřebuju Derived někam zkopírovat, vím jeho velikost a víc nepotřebuju. Pokud potřebuju šahat do Base, vím jeho offset, což je taky všechno, co potřebuju.

Re:Ideálny programovací jazyk
« Odpověď #74 kdy: 10. 05. 2019, 11:40:26 »
Měl jsem na mysli hlavně velikost známou až v runtimu. Jedna věc jsou dočasné proměnné. Ty jsou v pohodě, i když najednou nutně potřebujeme registr pro frame pointer. Druhá věc je ale vracení objektu neznámé velikosti hodnotou. Volající neví kolik paměti vyhradit a volaný to taky nezvládne alokovat bez větších problémů. Musel by ten zásobník přeskládat. Neznám žádné volací konvence, které by něco takového uměly.
Já ty lowlevel detaily dobře neznám a C++ jsem viděl naposledy tak před deseti lety, takže mi asi spousta věcí uniká, ale čistě od stolu mě překvapuje, že je to problém. Přijde mi, že by stačily dvě čísla na začátku: velikost Derived a offset Base v rámci Derived. Pokud potřebuju Derived někam zkopírovat, vím jeho velikost a víc nepotřebuju. Pokud potřebuju šahat do Base, vím jeho offset, což je taky všechno, co potřebuju.
Uložit někam velikost není problém. Ten je v tom, že na zásobník se ukládají i návratové adresy a parametry při volání funkcí. Takže volající na zásobník nacpe parametry a pak se tam uloží i návratová adresa. Pokud volající zná přesnou velikost vraceného objektu, tak si může vyhradit lokální proměnnou a předat její adresu jako parametr. Pokud tu velikost nezná, tak má smůlu.
Volaný si může na zásobník nacpat co chce, jenže před koncem musí všechno zase uklidit aby na vrchu byla návratová adresa. Jinak by se nešlo vrátit. Mohl by pošoupnout parametry a návratovou hodnotu a tím vyhradit místo. Tohle je komplikace nejen na straně volaného, ale i volajícího, kterému by se pod rukama změnil stack pointer.

Běžná a poměrně důležitá optimalizace je vypuštění frame pointeru. Tím se ušetří jeden registr a zjednoduší prolog a epilog funkce. Pokud nejde v compile-time přesně modelovat věci na zásobníku, tak tuhle optimalizaci nemůžeme udělat. Variable length arrays nebo alloca v C tuhle optimalizaci znemožní v rámci jedné funkce. Tohle by prosáklo i ven. A navíc takovéhle skoky neznámé velikosti ztěžují kompilátoru adresování ostatních proměnných na zásobníku.