Dynamische Datentypzusammensetzung



  • Hallodrio,

    sagt mal habt ihr eine Ahnung, wie Excel seine Datentypen und die der Zellen in Tabellen verwaltet?

    Ich habe im Moment das Problem, dass ich dynamisch Datenzusammenstellungen im Sinne von einzelnen Werten bestimmter Typen oder Strukturen erzeugen und verarbeiten möchte und die dann zum Beispiel tabellenweise abspeichern/weiterverarbeiten muss. Im Moment ist der Typ der Daten Bestandteil der Kommunikationsschnittstellen. Also es gibt String-Interfaces, Nummer-Interfaces, Zeit-Interfaces etc. Zur Laufzeit wird dann entschieden, ob die miteinander verwobenen Interfaces zueinander kompatibel sind.
    So geht das Tabellenziel jetzt davon aus, dass es immer Nummer-Daten für jede Spalte bekommt, eben weil es nur ein Nummer-Interface an die Datenquelle meldet.
    Es wäre im Moment nur über den Umweg von multiplen Schnittstellen möglich, auch andere Daten anzunehmen. Das war damals die Idee um multiple Typen unterstützen zu können. Allerdings, wo es jetzt in greifbare Nähe rückt, fällt mir auf, dass ich bei allen verarbeitenden Instanzen für alle Typen vorsorgen, prüfen und konvertieren muss.

    Es gibt noch viel mehr als Tabellen, aber hier ein Beispiel für die Tabelle:

    class TableColumn {
       virtual bla;
    };
    
    class NumberColumn :
      public TableColumn
    {
     ...
    };
    
    class StringColumn :
     public StringColumn
    {
     ...
    };
    
    class Table {
    
      std::vector< TableColumn > columns_;
      };
    }
    

    Nun interessiert die Tabelle selber ja eigentlich gar nicht der Datentyp der Zellen, bis auf die Art der Speicherung der Elemente natürlich.
    Genauso hätten auch andere Bestandteile nur gewisse Anforderungen an die Typen, die man "metaisieren" könnte, aber müssten trotzdem ihre Teile für jeden einzelnen Datentyp implementieren und wieder eine Basisschnittstelle definieren.

    Einerseits habe ich jetzt überlegt, ein festes Typsystem mit einem großen Typ-Enum einzubauen und dann überall so etwas feste Variants zu verwenden, immer auf größtmöglichem Maßstab natürlich, damit man nicht so viele Typids speichern muss. Also alles genau so machen, wie ich mir das damals vorstellte.

    Ich spiele aber auch gerade mit dem Gedanken, ein dynamisches Typsystem aufzusetzen, bei dem pro Typ angegeben wird, wie er kopiert wird, wie groß er ist, das alignment, virtuelle placement-news und deletes etc. Dann könnte man noch Konvertierungsoperatoren dynamisch registrieren etc, zum Beispiel zu String, XML, anderen Typen.
    So könnte ich Container bauen, die nicht zur Compile-Zeit auf einen Typ festgelegt sind, sondern für einen dynamischen Typen erzeugt werden können.

    Für die Typen könnte ich auch Element-Editoren festlegen, oder wenn nicht, könnte ein Editor auf String-Editierung zurückfallen oder einen Fehler melden, wenn kein Konvertierer für einen unterstützten Typen vorliegt.

    Vor allem hätte es aber keine Compile-Zeit Auswirkungen auf all die vorhandenen Klassen, wenn ich einen neuen Typ definiere. Vielleicht arbeiten sie mit ihm, weil er all die Anforderungen von konvertierbarkeit/Speicherbarkeit erfüllt, oder das Typsystem kann selber feststellen, dass man hier etwas falsches versucht.

    Das ist natürlich ein ziemlich drastischer Ansatz und kommt der Simulation einer kleinen Programmiersprache irgendwie nahe (System in a System usw. lässt grüßen hehe). Andererseits ist das, was ich auf dieser Basis mache ja im Prinzip auch eine Programmiersprache.

    Habt ihr Vorschläge? Ich habe mich bisher durch ein paar Artikel zur Simulation von Metaklassen durchgewuselt, aber das traf alles nicht unbedingt auf meine Situation zu. Und andere haben sowieso immer viel bessere Ideen 😃 Mir fehlt hier einfach der Gesprächspartner, mit dem ich mich über all die Ideen in die Wolle bekommen kann 😃

    Viele Grüße,
    Deci



  • Decimad schrieb:

    Einerseits habe ich jetzt überlegt, ein festes Typsystem mit einem großen Typ-Enum einzubauen

    So hab ich das das letzte mal gemacht, allerdings allerdings gab das ganze dann einen Baum, du hast nur eine flache Liste.

    Decimad schrieb:

    Ich spiele aber auch gerade mit dem Gedanken, ein dynamisches Typsystem aufzusetzen, bei dem pro Typ angegeben wird, wie er kopiert wird, wie groß er ist, das alignment, virtuelle placement-news und deletes etc. Dann könnte man noch Konvertierungsoperatoren dynamisch registrieren etc, zum Beispiel zu String, XML, anderen Typen.

    Du musst überlegen, was zu dem Typen genau gehört. Die Konvertierung ist eindeutig eine Sache vom Format und nicht vom Typ.

    Decimad schrieb:

    Andererseits ist das, was ich auf dieser Basis mache ja im Prinzip auch eine Programmiersprache.

    Die Frage ist ja immer, was ist performance-kritisch. Eine String-Editierung gehört da eindeutig nicht dazu.
    Andere Sachen vielleicht schon. Wenn du viel kopieren musst, ist vielleicht eine Immutable-Struktur besser, dann ändert sich wieder alles. Die Entscheidung beruht im Endeffekt auf dem, was du gerne optimiert haben möchtest und wie viel Komfort du haben möchtest.

    Also rücke etwas mehr Informationen raus, für was du das genau brauchst.



  • Hallo!

    Nun, im Moment ist das eine Echtzeit-Datenerfassungs-, -Verarbeitungs- und Ausgabe-Software, die man graphisch konfigurieren kann. Think Labview, aber sehr stark in Richtung Signale mit physikalischer Semantik und deren Darstellung auf das allernötigste heruntergespeckt und nicht als visueller Ansatz graphisch allgemeine Programme zu schreiben (wovon ich überhaupt nichts halte, für solche Fälle habe ich Lua-Plugin-Knoten, wenn's unbedingt sein muss).

    Im Moment werden da im Datenverarbeitenden Teil (es werden auch andere Signale ausgetauscht, Toggles, Bools, Widgets, die Tabellen selber, aber das kann alles mit den Schnittstellen so bleiben) Zahlenwerte mit Namen und Einheit ausgetauscht, die als Tripel zusammen ein Datensignal ergeben. Das macht es einfach, weil jeder verarbeitende Knoten eben nur alles für diesen einen festen Typ Signal machen braucht. Ich würde jetzt aber wie gesagt gerne den Signalen zusätzliche Attribute (meinetwegen eine weitere Zahl, oder eine Farbe, ein String), die aus anderweitiger Berechnung stammen, zuweisen können (Felder, und damit eine hierarchische Zusammenfassung wäre aber auch toll). Außerdem möchte ich nicht alles auf Zahlenwerte runterbrechen müssen. Das bedeutet aber gleichzeitig, dass Signale "dynamische" Struktur erhalten, also es ist erst kurz vor der Ausführungszeit bekannt, aus welchen Bestandteilen sie bestehen. Der Tabelle, die das abspeichert, soll es beispielsweise wurscht sein, Berechnungsknoten picken sich halt kompatible Bestandteile raus (auswählbar durch den Benutzer) und schleifen den Rest durch, so man denn will.
    Ein Signal würde halt aus einer Anzahl von benannten Attributen (also zeitkonstante Werte, wie zum Beispiel Einheit) und zeitveränderlichen Werten bestehen, wobei diese alle eben auch noch unterschiedliche Typen haben dürfen.
    Und da würde ich halt eine Lösung bevorzugen, die den Implementierungsaufwand an den einzelnen Knoten minimiert.

    Die Umwelt sieht im aktuellen Anwendungsfall so aus, dass bis zu 2000 (mit jeweils 20 Kanälen) Datenpakete pro Sekunde aus 4 Quellen reinprasseln, ein bissl rumgerechnet wird und sich die CPU-Auslastung hauptsächlich aus der graphischen Darstellung ergibt. Aber daran arbeite ich auch schon 😃



  • Hrmmm, ich verfolge jetzt eher den Gedanken, dieses Typsystem so zwar umzusetzen, weil es echt flexibel ist und für die Benutzerinteraktion mit einem dynamischen Typsatz praktisch prädestiniert ist, aber irgendwie eine template-isierte Methode für Fastpaths zu bieten (natürlich nur für Typen, die der Quellcode des Systems kennt), die dort, wo es benötigt wird, wenig Aufwand macht.
    So kann ich mich auf die wenigen wirklich wichtigen Datentypen and den wichtigen Stellen stürzen und ansonsten alles "generisch" handhaben.
    Muss... ausprobieren... Befürchte nur, dass ich da Zeit reinversenke und am Ende kein größerer Erkentnis- und Nutzengewinn entsteht, weil ich dann doch abbreche... grummel. Sowas passiert mir zu oft!



  • Dazu zwei Fragen:
    Kann man C++ irgendwie mitteilen, dass die virtuellen Methoden einer Schnittstelle "statischen" Charakter haben, also dort kein this-Zeiger benötigt wird, per Kontrakt auch von allen Implementierungen?
    Außerdem, kann man irgendwie sagen, dass eine bestimmte Implementierung final ist, so dass ein Template mit Zeiger darauf die ganzen Funktionen inlinen kann?
    Wäre echt praktisch, wenn man sowas machen könnte.



  • Decimad schrieb:

    Dazu zwei Fragen:
    Kann man C++ irgendwie mitteilen, dass die virtuellen Methoden einer Schnittstelle "statischen" Charakter haben, also dort kein this-Zeiger benötigt wird, per Kontrakt auch von allen Implementierungen?
    Außerdem, kann man irgendwie sagen, dass eine bestimmte Implementierung final ist, so dass ein Template mit Zeiger darauf die ganzen Funktionen inlinen kann?
    Wäre echt praktisch, wenn man sowas machen könnte.

    Geht nicht (wenn ich dich richtig verstehe und nichts wichtiges überflogen habe) und würde ich auch nicht empfehlen. Spätestens beim Kopieren bekommst du Probleme, weil deine Klassen nicht trivially copyable sind.

    Ich würde Vererbung hier ganz C-like nachstellen, dann bleibst du bei PODs.

    typedef int type_id; // 0 für base, >0 für statisch bekannte, <0 für dynamisch hinzugefügte
    
    template <typename T> struct excel_traits;
    
    struct base {
      template <typename T>
      base() id(excel_traits<T>::type_id) {}
    
      type_id id;
    };
    template <> struct excel_traits<base> { enum{ type_id = 1; }};
    struct integer : base {
      integer(int val):base(integer_id){}
      int value;
    
      void do_sth();
    };
    int get_size(type_id id); // if (id>0) switch/case, else lookup in map
    int get_alignment(type_id id); // ditto
    
    class visitor; // geht, auch mit switch
    template <typename T> T& is(base& b){ return b.id == excel_traits<T>::type_id; }
    template <typename T> T& cast(base& b) { assert(is<T>(b)); return static_cast<T>(b); }
    
    std::map<type_id, std::function<std::string(void*)> to_xml;
    std::map<type_id, std::function<std::string(void*)> to_csv;
    
    void do_sth_with_it(base& b) {
      if (is<integer>(base)) {
        integer& i = cast<int_id>(b);
        i.do_sth(); // kein dispatch
      } else {
        ...
      }
    }
    


  • Irgendwie ist mir das zu viel Text zum Lesen. Du willst im Endeffekt ein Variant haben? Entweder über eine Basisklasse, wie du es wohl angefangen hast, oder du verwendest wirklich sowas wie ein Variant, z.B. QVariant aus Qt oder boost::any. Wie man das macht kommt sehr stark auf die Anforderungen an, und was du damit alles machen willst und worauf du wert legst. Kannst du versuchen, das möglichst knapp zusammenzufassen?



  • Nun, ich weiß ja noch gar nicht so genau, was ich haben will, bin da noch in der Experimentierphase.
    Was ist damit ERREICHEN will, kann ich eben nur in Sätzen erklären, wie ich es eben getan habe.

    Ich möchte Daten durch Kanäle schicken, deren Typ erst zur Laufzeit bestimmbar ist. Boost::any oder die Variants passen nicht so ganz in mein Schema. Erstmal habe ich keine Lust mehr auf aufgeblasenes Template-TypeList-Funktional-Gedöns, also an sich eine gewisse Abneigung gegen viele Boost-Ansätze entwickelt (zumindest wenn man dann auch noch damit in Berührung kommt, mag vieles auch an fehlenden Variadics liegen...), zweitens ist es bei mir nicht so, dass ich pro Datum den Typ entscheiden müsste. Ab dem Zeitpunkt, an dem die Daten zu fließen beginnen, ist der Typ fix. Daher wollte ich eher mit TypeInfo-Strukturen oder Klassen arbeiten, zu denen noch Meta-Information (Konvertierungen, etc.) hinzugefügt werden können.
    Bevor es losgeht, kann also alles auf Durchführbarkeit gecheckt werden und die nötigen Operationen so gut es geht aus den TypeInfo-Daten bzw. Meta-Daten gepuffert werden. Ab da ist dann alles statisch, bis auf die Daten selber natürlich. Die selbe Situation letztlich, der sich ein statisch typisierter Compiler auch gegenübersieht.
    Einzelne Instanzen müssten dann nur noch wissen, dass ein bestimmter Typ Vergleichbar ist und könnten dessen Operationen verwenden, um Maxima auszurechnen oder eine sortierte Liste zu erzeugen, wobei die TypInfos nur einmal für alle Elemente gespeichert werden müssen. So würde ich mir, wo es wegen absoluter Performanz nicht sein muss, auch die ätzenden switch-Blöcke sparen.

    Ich habe jetzt mal angefangen das ganze für Integer und Strings durchzuspielen, um einmal ein Bild von allen Seiten zu erhalten, und das dann mit einem pur-statischen enum-Ansatz vergleichen zu können (wobei ich da immer noch nicht weiß, wie ich da ohne Variants Daten durch Interfaces schleifen kann). Gibt es irgendwie Variants, die die Typ-Information einfach nicht mitspeichern, sondern bei denen man das über andere Kanäle vermitteln kann?


Log in to reply