Kompozíció statikusan típusos, imperatív programozási nyelvben

Fórumok

Üdv!

Valószínűleg megfertőztek a "modern" programozási paradigmák, mert egy (szerintem) egész egyszerű problémát nem sikerül megoldanom a topicban jelzett tulajdonságú nyelvben (egész pontosan GLSL, de ez most irreleváns).

A probléma demonstrálására kitaláltam egy egyszerű feladatot: szimbolikus algebrát kell megvalósítani a négy alapművelettel, konstansokkal, változókkal, lehetőleg bővíthető módon. A változók behelyettesítési értéke a kifejezés létrehozása után kerül megadásra, vagyis a kifejezésnek többször felhasználhatónak kell lennie. A problémát mindjárt meg is oldottam OOP és funkcionális szemléletben is, mindkét esetben Scalaban. :) A bővíthetőséget az sqrt függvény demonstrálja, ami a SymbolicExpressions objektumon kívül került megvalósításra, vagyis az eredeti kódhoz nem kellett nyúlni.

Egyaránt várok ötleteket és konkrét megoldásokat. Utóbbi esetben ha a választott nyelv C, akkor nem ér makrókat, fordítóspecifikus feature-öket, függvény pointereket, uniont és idegen típusra castolást (lásd alább) alkalmazni. Más nyelv esetén előbbi szabályok értelem szerűen alkalmazandóak. A lényeg, hogy a megoldás tiszta, szép, a nyelvet nem megerőszakoló kód legyen.


Foo* foo = newFoo();

// nem ér, Bar != Foo
Bar* bar = (Bar*)foo;

// ér, minden pointer egyben void* is
void* baz = (void*)foo;

// ér, baz eredetileg Foo*
Foo* quz = (Foo*)baz;

Hozzászólások

Nem értem.

Objektum-orientált virtuális metódushívást C-ben nem tudsz csinálni függvénypointerek nélkül, csak ha felsorolod a lehetséges opciókat (pl. egy switchben elágazol a típustól függően, és az n különböző osztályhoz tartozó metódus-függvényeket belefordítod ebbe a switchbe) - így viszont a bővíthetőséget buktad el. Lehet random egyéb megoldásokkal trükközni (pl. makróbűvészkedés, scripttel generált forráskód, dinamikus linker, stb.), de ez a korrekt megoldás. A "szép" kitételt nem is értem ennek kontextusában: kevés "szebb" dolog van a C-ben a függvénypointereknél.

Ergó első ránézésre az, amit szeretnél, C-ben pl. lehetetlen. Nem azért, mert a nyelv nem tud eleget, hanem mert kellően sok constraintet raktál a saját nyakadba, így üres halmaz maradt...

+1
Szerintem se megoldható az eredeti kódmódosítás bővíthetősége vagy függvény pointerek nélkül. A kettő közül egy kell, hogy bővíthető megoldást kapj.
BaT, azt megkérdezhetem, hogy szerinted a statikusan típusos nyelvet vagy az proceduralitást a függvény pointer hol töri meg?
Üdv,
LuiseX

Az egyszerű bővíthetőségről hajlandó vagyok lemondani, a követelmény úgy szól, hogy lehetőleg bővíthető módon.

A függvénypointerekkel csak az a baj, hogy nem minden nyelv támogatja, illetve azt szeretném megakadályozni a megszorítással, hogy OOP megoldás szülessen, OOP megközelítéssel én is le tudom a feladatot implementálni, nem az a cél. :)

Szia,
Hát, igazából a függvénypointert minden C alapú nyelv támogatja :)
De ilyen alapon Object-ekkel sem szabad gondolkoznod, illetve Arrayekkel sem... Ha egy nyelvet már kiválasztasz a feladatra, szerintem érdemes annak az eszközeit használnod, mintsem túlzott megkötésekkel élned...
De szerény véleményem szerint, ha az OOP-t teljesen kiakarod zárni, akkor már érdemesebb egy jól felépített állapotgépet implementálni a feladatra ( ld. https://en.wikipedia.org/wiki/Finite-state_machine, de C-ben ebből kb. egy switch case-t fogsz látni ideális esetben, mint például itt is : http://www.javakode.com/c-programming/c-code-for-calculator-application/ ).
Remélem, sikerült részben segítenem,
Üdv,
LuiseX

Rendben. Fragment shadert szeretnék írni, Shadertoy-ban, a magam szórakoztatására. A shader egy egyszerű raytracer lenne, amihez kitaláltam egy egyszerű 3D objektum reprezentációt, aminek annyi a lényege, hogy féltereket definiálok, amik tulajdonképpen vec4-ek. Ezekből a félterekből szeretnék ezután unió és metszet segítségével komplex objektumokat definiálni. Például egy kockát 6 féltér metszete határoz meg. Egy ilyen objektumot függvényként nagyon egyszerű leírni, de én szeretném szétválasztani és objektumként reprezentálni őket, és ezekre az objektumokra megvalósítani a megjelenítéshez, transzformáláshoz, stb. szükséges függvényeket.

Értelek. Akkor szembesültél azzal a problémával, amiért nem igazán használnak hasonszőrű megoldásokat modern engine-ekben:)
Egyrészt azért, mert a GLSL kódod közvetlen GPU kódra fordul, így igazából teljesen mindegy hogy mivel próbálkozol, a vége az lesz hogy elágazások lesznek belőle.
A másik probléma az, hogy egy fragment shadernek nehéz áttolni az adatokat. Az uniform-ok száma véges (GL_MAX_UNIFORM_LOCATIONS), illetve lehet arrayként is használni uniform-okat, viszont az array elemei és végesek (GL_MAX_VERTEX_UNIFORM_VECTORS).
A shadertoy pedig egyébként sem olyan rugalmas mintha közvetlen az opengl-t hajtanád.

A shaderekre microcode-ként szabad csak tekinteni, vagyis amit szeretnél megvalósítani az ebben a formában nem csak hogy nem lehetséges, de nem is ajánlott. Ha mindenféle módon szeretnéd ezt megvalósítani, akkor úgy jársz a legjobban ha csinálsz egy preprocessort ami egy kiterjesztett nyelvből csinál neked GLSL kódot, amiből végül a GPU kód áll elő.
Vannak hasonlók (de nem ezt valósítják meg), pl nvidia CG.

Szóval nem azt mondom hogy nem lehet, de nem ez a jó megoldás hogy megpróbálod "elrejteni" a dolgok valódi működését.

// Happy debugging, suckers
#define true (rand() > 10)

Ez rendben van, nem is az a cél, hogy egy profi engine-t írjak, csak ki szeretnék próbálni egy koncepciót. Vagyis nem baj, ha lassú lesz, vagy ha belefutok olyan akadályokba, amik megoldása nem kifizetődő. Viszont feltételeztem, hogy a topicban vázolt probléma egyrészt megfogalmazható kellően általánosan ahhoz, hogy a megoldás azután átfordítható legyen GLSL-re, másrészt ez már egy megoldott probléma, hiszen ez a programozási paradigma évtizedekig uralta a szoftverfejlesztés világát és még ma is sok helyen aktívan használatban van.

Nem is arról beszéltem hogy írj profi engine-t, csak az általad vázolt igényeket szegény GLSL-be nem lehet beleszuszakolni.
Amit tehetsz, használsz struct-ot és inline metódusokat (hogy legalább gyors legyen) és maszkolsz (pl írsz rá egy preprocesszort)

// Happy debugging, suckers
#define true (rand() > 10)

Szia,
Amit linkeltem megoldás, az sem teljesen C specifikus (mármint nem épít pointerekre/makrókra/bármire amit csak C-ben lehetne csinálni, csak c függvényeket és keywordoket kell átírni, hogy tetszőleges nyelvre váltsd), bár tény a másik általam vélt kívánalmadat, az operátorok adatjellegű kezelését nem valósítja meg ( Bár tény, hogy abban a kódban a bal és jobb értékek az adatok, az operátorok pedig művelet jellegűek ) - igaz, lehet egy lépésre van attól még, amit pontosan szeretnél: ez stdio-ra van kihegyezve, neked pedig string alapú feldolgozás kellene, de szerintem ebből a mintából nem teljesen lehetetlen tovább lépni arrafelé...
Sajnos, ez utóbbiból kész példát nem nagyon találtam, ami nem tartalmaz objektumokat ( holott, nem lenne szükséges, sőt, egyszerűen áthidalható lenne azok jelenléte is ).
Ellenben GLSL-hez nem nagyon értek, így kétlem hogy abban a részben érdembeli segítséget tudnék adni :)
Üdv,
LuiseX

Ez nem teljesen igaz, lehet vtable-t implementalni C-ben is, makrokat hasznalva pedig viszonylag olvashato C++-szeru kodot is lehet irni, oroklodessel meg mindennel. Itt egy pelda ahol nincsenek makrok a topicszerzo kedveert :) Az egyetlen "csunya" dolog, hogy ki van hasznalva hogy a struct elso memberje ugyanaz, de C-ben ez nem ordogtol valo.


#include <stdio.h>

// Interface for A and B
struct Vtable {
  void (*func)(void*, int);
};


// class A
struct A {
  struct Vtable* vtable;      // note - this should be the first member
  int x;
};


// class B
struct B {
  struct Vtable* vtable;      //  first member
  int x, y;
};


// A::func
void A_func(void* _self, int d) {
  struct A* self = _self;
  self->x += d;
}


// B::func
void B_func(void* _self, int d) {
  struct B* self = _self;
  self->x += d * 2;
  self->y += d * 2;
}


// vtable for A
struct Vtable* A_vtable() {
  static struct Vtable vtable = {
    .func = A_func
  };

  return &vtable;
}


// vtable for B
struct Vtable* B_vtable() {
  static struct Vtable vtable = {
    .func = B_func
  };

  return &vtable;
}


// A::ctor
void init_A(struct A* self) {
  self->vtable = A_vtable();
  self->x = 0;
}


// B::ctor
void init_B(struct B* self) {
  self->vtable = B_vtable();
  self->x = 0;
  self->y = 0;
}


// convenience for calling 'func'
void call_func(void* obj, int n) {
  struct Vtable* vtable = *(struct Vtable**) obj;
  vtable->func(obj, n);
}


int main() {
  struct A a;
  struct B b;

  init_A(&a);
  init_B(&b);

  call_func(&a, 5);
  call_func(&b, 5);

  printf("%d\n", a.x + b.y);
}

Mi lenne, ha nem OO-ban gondolkozva próbálnád megoldani. Mondjuk egy csúnya ötlet: csinálsz egy fát, ahol egy node a művelet, és amikor bejárod a fát, azzal oldod fel. A leaf a bemeneti paraméter. A kód pedig bővíthető lesz, csak nem örökléssel, hanem egy helyen történő bővítéssel. :)

http://www.geeksforgeeks.org/expression-tree/

Na valami ilyesmire gondoltam én is, hogy külön van választva az adat és a viselkedés. Csak annyi a gondom vele, hogy így is különféle adattípusokra van szükségem, mert például a négy operátoron kívül a többinek nincs két gyereke, a konstans egy lebegőpontos értéket tárol, míg a változó semmit (önmagát definiálja). Erre első körben egy ilyen megoldás jutott eszembe, de elég bénának érzem. A Type enumok számomra azt jelzik, hogy még mindig OOP megoldást próbálok megvalósítani, az egész topic tulajdonképpen arról szól, hogy milyen más megoldás lehetséges.

Kitaláltam egy megoldást a castolás elkerülésére, igaz ettől még merevebb lett a kód. Ne tévesszen meg senkit, hogy a nyelv Scala, direkt nem használtam a legtöbb nyelvi és standard library szolgáltatást. Egy mini DSL segít, hogy az adatstruktúra könnyen felépíthető legyen, igaz egy rakás kódduplikáció árán. Az lenne a jó, ha a store-ok kezelhetőek lennének egységesen, de mivel mindegyik store tömbje más típusú, generikusok/templatek/makrók/castolás nélkül nem látom, hogyan lehetne megoldani.