Was stört/fehlt euch bei aktuellen Programmiersprachen?



  • Xin schrieb:

    Nein, das wäre eine Symptombehandlung.

    Für 4 Zeilen C++ Code, die nicht fehlschlagen konnten, brauchte ich 2 Seiten Java-Quelltext, weil ich diszipliniert jede Exception abarbeiten musste, die per 'throws' vorgegeben waren.

    Alleine die Trennung von Fehlerauftritt und Fehlerbehandlung halte ich für falsch - das muss zusammenbleiben.
    Das Springen über die Funktionshierarchie hinaus, ist eine Katastrophe.

    Vielliecht hast Du mal was von INTERCAL gehört. Das ist eine Programmiersprache aus den 70ern. Bekannt wurde sie für das "COME FROM"-Statement, ein inverses Goto. Erreicht das Programm das Label, das bei COME FROM angegeben ist, verzweigt es an die Stelle, wo das COME FROM im Quelltext steht.

    Etwa so:

    printf( "1\n" );
    Label:
    
      printf( "2\n" );
    
      come from Label;
    
      printf( "3\n" );
    

    Ausgabe:
    1
    3

    INTERCAL hatte nur einen Zweck: Die Programmierung so umständlich und schlecht wie möglich zu machen. Eine reine Demonstration, wie man es nicht machen soll.

    Und jetzt ersetze mal "COME FROM" mit "catch" und vor das Label schreibst Du "throw"... 😉

    Und wie behandelst du dann Fehler, bei denen am Zeitpunkt des Auftretens nicht bekannt ist, wie sie behandelt werden sollen? Gerade das macht Exceptions doch so praktisch.

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Ich programmiere noch nicht mal 10 Jahre. Damals hatte ich auch noch keinen Computer :p

    Warum möchtest Du dann eine Programmiersprache schreiben?

    Weil ich denke, dass ich ein paar gute Ideen habe und aktuelle Sprachen alle irgendwelche Problme haben.

    Und reichen die Ideen, um den Aufwand zu rechtfertigen?

    Ja.

    Xin schrieb:

    Kellerautomat schrieb:

    final ist gut als Optimierungsmöglichkeit, daher sind bei mir alle Klassen und Methoden per default final.

    Spricht ja nicht gegen Optimierung, solange man das final zurücknehmen kann. ^^

    Man muss es eben explizit sagen. Selbst wenn eine Funktion in der Basklasse überschreibbar ist, wird sie in einer abgeleiteten Klasse automatisch wieder final.

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Das ist eine Referenz, die Null sein darf.

    Im Gegensatz zu (Foo?) muss man aber nicht die Information mitführen, ob wie bei (int?) 0 0 oder Null bedeutet. Das ganze macht also nur Sinn, wenn Null ein valider Wert wäre, der sich von Null unterscheidet. Und wenn man über den vorherigen Satz nachdenken muss, um ihn zu verstehen, sollte man sowas im Quelltext vielleicht expliziter ausdrücken.

    T? nimmt alle Möglichen Werte von T sowie den Wert null auf. Was ist daran schwer?

    Nichts. Aber wo braucht man es, außer bei Primitiven?

    Ist das ein Konstrukt wert?
    Ist das ein ? wert?
    Wenn Du viele Ideen hast, wird Dir auffallen, dass Kurzzeichen-Operatoren eher Mangelware sind.

    Ein Zeiger kann viel mehr als ein Nullable. Er kann auch einfach irgendwohin zeigen, das kann ein Nullable nicht. Ausserdem haben Nullable spezielle Operatoren. Das Feature des Nullable liegt darin, dass die Semantik beschränkt wird auf das Wichtige.

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    let m1 := new HashMap!<string, int> // Leere Map
    

    Wofür steht das '!'?

    Dient dazu, um Mehrdeutigkeiten in der Grammatik aufzulösen.

    Mehrdeutigkeiten? Welche könnte das sein?
    Es ist doch Deine Grammatik und da sollte die Sache doch möglichst eindeutig sein?

    Die können entstehen, wenn der Benutzer eigene Operatoren definieren kann. Ich bin mir aber auch bei der template-Syntax noch nicht sicher.

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Gratulation für die Abkehr von '=' für die Zuweisung.

    Wie schon mal gesagt: ":=" heißt Initialisierung, "=" ist eine Zuweisung.

    Oh... schade. ^^

    Was willst du damit sagen? 😕

    Xin schrieb:

    Kellerautomat schrieb:

    Das hat insbesondere Voteile in Konstruktoren: Man kann die Initialisierung im Funktionskörper durchführen und braucht keine Initialisierungsliste mehr. Außerdem ist die Initialisierungsreihenfolge egal.

    let f1 := Foo(1);
    let f2 := Foo(2);
    
    f1 := f2;
    

    Was bedeutet das?

    Nichts, das ist ungültiger Code. In Codezeile 3 hättest du = verwenden müssen.
    Die Semikola sind übrigens optional, ein \n in der Datei reicht.

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Wie lange programmierst Du und was hast Du so programmiert?

    Ich hab im Alter von 11 Jahren angefangen, C++ zu lernen.

    Interessanter Zeitpunkt, dann verstehst Du Programmiersprachen vermutlich anders als die meisten hier.

    Fang mal an und halt mich auf dem Laufenden. Interessiert mich. Wenn Du Fragen hast, auf unten genanntem Forum lese ich alle Boards und bin unter UsernameHier at proggen.org zu erreichen, hier lese eigentlich nur "Beruf und Ausbildung" und ansonsten nur wenn ich Zeit habe und/oder zufällig was Interessantes mitbekomme.

    Mich würde interessieren, was du mit "anders" meinst 😉

    Wenn ich heute Abend Zuhause bin, reistriere ich mich mal dort. Könnte auch einen Thread zu meiner Sprache aufmachen.

    Edit: Bist du eigtl. evtl. irgendwo im IRC unterwegs?



  • Ich hätte gerne ein C++ mit weniger Sonderregeln und dafür eine höhere Regularität.

    Man muss hier und da immer noch zuviel schreiben, gerade wenn man generischen Code schreiben will und man die ganzen Typen, mit denen man zu tun hat, gar nicht kennt. Mehr Deduktion bitte.

    Ich hätte gern implicit statt explicit .

    Ich hätte gerne ein C++ ohne diesen ganzen Initialisierungs-Blödsinn (default, value, zero, direct, copy, direct-list, copy-list, ...).

    Ich hätte gerne ein C++ wo all die Sachen die in C++11 deprecated sind, rausgeflogen sind, damit auch Anfänger nicht mehr die Dreierregel verletzen können, auch wenn sie davon noch nie gehört haben.

    Das, was C++ leistet finde ich schon ganz nett. Aber die Form, in der man es aufschreiben muss ist irgendwie doof.

    Ich wünschte, der ganze Metaprogrammierungskram wäre leichter. Da muss man ja echt krasse Verreknungen machen teilweise. Otto Normalo wird sich daran nicht stören, aber einen Entwickler generischer Bibliotheken nervt das sicher.

    Ich hätte gerne einen Debug-Modus, der mir so etwas wie einen weak_ptr für einen unique_ptr geben kann, der dann automatisch 0 wird, wenn der unique_ptr das Ding löscht und dann bei Dereferenzierung in so einem Zustand den Debugger aktiviert. Nur zwecks Fehlererkennung. Im Release-Mode könnte das ja wieder ein raw-Pointer sein. So könnte man noch dangling-pointer-Probleme in Verbindung mit unique_ptr früher erkennen.

    Ich hätte gern ein vernünftiges Modul-System, ohne diesen ganzen Header-Krampf. Dann kann man vielleicht auch wieder eine using-Direktive verwenden, weil der using namespace-Kram dann ja nicht in die Schnittstellen-Datei exportiert werden muss.

    Ich hätte gerne concepts light2.

    Ich hätte gerne ein C++, was sich flotter kompilieren lässt. Vielleicht hilft ein ordentliches Modul-System da schon aus.

    Ich hätte gern eine bessere Tuple-Unterstützung. Zum Beispiel so etwas wie folgende syntaktische Abkürzung

    tuple<int,int&,double&> get_stuff();
    
    int main() {
      {a,b,const& c} = get_stuff();
      cout << a;
      cout << b;
      cout << c;
    }
    

    für so etwas:

    tuple<int,int&,double&> get_stuff();
    
    int main() {
      tuple<int,int,const double&> abc = get_stuff();
      cout << std::get<0>(abc);
      cout << std::get<1>(abc);
      cout << std::get<2>(abc);
    }
    

    Ich hätte gern ein ordentliches Range-System (das kann man noch auf Bibliotheksbasis machen), so dass man z.b. so Dinge schreiben kann wie

    iunt main()
    {
      vector<double> foo = get_other_stuff();
      for ({index,&value} : indexed(foo)) {
        cout << index << " --> " << value << endl;
      }
    }
    

    wobei es dann noch viele schöne andere "Range-Adapter" gibt...

    😃



  • ich haette es auch gerne effizienter vom tippen her.

    1. wenn schon accessor als sauberer stil angepriesen sind, dann sollte man sie effizienter definieren koennen bis man mehr als den reinen access braucht z.b.

    class foo
    {
    int m_Bar:bar;
    };
    
    ...
    foo F;
    F.bar(1);
    return F.bar();
    

    ich wuerde gerne typesafe enum operatoren haben, wenn es sowas wie

    enum EFlags;
    {
     EF_01= 1<<0,
     EF_10= 1<<1,
     EF_11= 3,//EF_01|EF_10
    };
    

    gibt, sollte man auch sowas schreiben koennen

    EFlags Flags(){return EF_01|EF_10;}
    

    zZ hat man ugly type cast falls man das moechte oder man muss auf einen generischen typ umsteigen und verliert damit die 'safety' was gerade bei funktionsueberladen schiefgehen kann.

    Flux(EF_10);
    Flux(EF_01|EF_10);
    

    was bauzeit angeht, das ist aber mehr ein compiler wunsch, eine differenzierung zwischen verschiedenen header/inl etc. dateien. es sollte rein deklarierende dateien geben die nicht includen duerfen und was man braucht kommt von der translation unit oder wird per forward declaration bestimmt. es sollte auch 'inl' geben die auch nicht includen duerfen, sie haben nur definitionen der deklarationen die dann (wie der name sagt) inlined werden.
    schon mit diesen beiden 'features' koennte man das meiste an header include hell loswerden.
    c++ builds sind sehr davon abhaengig wie man programmiert, ich habe zuhause wirklich grosse projekte die ich aufgeraeumt habe und die compile time ging von ein paar minuten auf <10s runter. (ja, natuerlich hab ich multi-prozessor builds eingebaut und unity/ueber files, aber sehr viel ging aufs konto von includes).

    im allgemeinen wuerde ich mir wuenschen mehr features zu haben die schnelleres coden erlauben, ich fuehle mich irgendwie staendig ineffizient. (weil mein kopf 10mal mehr generiert als ich hinklatschen kann).



  • @Xin: Natuerlich ist das Kritik. Ich verstehe sie nicht als destruktiv. Wenn du sie nicht hoeren moechtest, dann unterhaltet euch doch privat, weil das Forum oeffentlich ist.



  • Kellerautomat schrieb:

    Xin schrieb:

    INTERCAL hatte nur einen Zweck: Die Programmierung so umständlich und schlecht wie möglich zu machen. Eine reine Demonstration, wie man es nicht machen soll.

    Und jetzt ersetze mal "COME FROM" mit "catch" und vor das Label schreibst Du "throw"... 😉

    Und wie behandelst du dann Fehler, bei denen am Zeitpunkt des Auftretens nicht bekannt ist, wie sie behandelt werden sollen? Gerade das macht Exceptions doch so praktisch.

    Vereinfach ausgedrückt: "false".

    Wenn die rufende Funktion kein Ergebnis bekommt, kann sie das vielleicht reparieren.

    Wenn ich aber eine Exception schmeiße und 5 Funktionen über mir kommt eine "Division by zero" Exception an, dann kann die damit auch nix mehr anfangen. Ich gehe schön brav Step by Step den Stacktrace hoch. Ein Schritt nach dem anderen, niemals zwei auf einmal. Jeder darf mal kurz überlegen, ob die Rückgabe der Funktion für ihn existenziell wichtig ist, oder ob man da noch was machen kann.

    Kellerautomat schrieb:

    Xin schrieb:

    Und reichen die Ideen, um den Aufwand zu rechtfertigen?

    Ja.

    Always listen to experts.
    They tell you what can't be done and why.

    Then do it.

    Wenn Du das das erste Mal geschafft hast, siehe Signatur. 😉

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    final ist gut als Optimierungsmöglichkeit, daher sind bei mir alle Klassen und Methoden per default final.

    Spricht ja nicht gegen Optimierung, solange man das final zurücknehmen kann. ^^

    Man muss es eben explizit sagen. Selbst wenn eine Funktion in der Basklasse überschreibbar ist, wird sie in einer abgeleiteten Klasse automatisch wieder final.

    Dass man Ableitungen verbietet, mag ich nicht.

    Dass man die Überschreibbarkeit für Ableitungen fordern muss... darüber muss ich nachdenken, ob ich das für eine gute Idee halte.

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Das ist eine Referenz, die Null sein darf.

    Im Gegensatz zu (Foo?) muss man aber nicht die Information mitführen, ob wie bei (int?) 0 0 oder Null bedeutet. Das ganze macht also nur Sinn, wenn Null ein valider Wert wäre, der sich von Null unterscheidet. Und wenn man über den vorherigen Satz nachdenken muss, um ihn zu verstehen, sollte man sowas im Quelltext vielleicht expliziter ausdrücken.

    T? nimmt alle Möglichen Werte von T sowie den Wert null auf. Was ist daran schwer?

    Nichts. Aber wo braucht man es, außer bei Primitiven?

    Ist das ein Konstrukt wert?
    Ist das ein ? wert?
    Wenn Du viele Ideen hast, wird Dir auffallen, dass Kurzzeichen-Operatoren eher Mangelware sind.

    Ein Zeiger kann viel mehr als ein Nullable. Er kann auch einfach irgendwohin zeigen, das kann ein Nullable nicht. Ausserdem haben Nullable spezielle Operatoren. Das Feature des Nullable liegt darin, dass die Semantik beschränkt wird auf das Wichtige.

    Ein Nullable kann Null sein oder nicht. Ein (int*?) könnte entsprechend Null sein oder nullptr oder irgendwo hin zeigen. Das ist nicht semantisch gleichwertig zu (int *), aber ob es ein Mehrwert ist?
    Bei int, float und enum ist es unbestritten ein Mehrwert - nur halt die Frage, ob das die Implementierung rechtfertigt und damit die Notwendigkeit das Konstrukt zu erlernen.

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Gratulation für die Abkehr von '=' für die Zuweisung.

    Wie schon mal gesagt: ":=" heißt Initialisierung, "=" ist eine Zuweisung.

    Oh... schade. ^^

    Was willst du damit sagen? 😕

    Dass ich es begrüßt hätte, wenn der '='-Operator nicht für als Kopier-Operator missbraucht wird, der von Anfängern dann mit dem '=='-Operator verwechselt wird.

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Das hat insbesondere Voteile in Konstruktoren: Man kann die Initialisierung im Funktionskörper durchführen und braucht keine Initialisierungsliste mehr. Außerdem ist die Initialisierungsreihenfolge egal.

    let f1 := Foo(1);
    let f2 := Foo(2);
    
    f1 := f2;
    

    Was bedeutet das?

    Nichts, das ist ungültiger Code. In Codezeile 3 hättest du = verwenden müssen.

    Warum unterscheidest Du dann im Quelltext zwischen Zuweisung und Initialisierung?
    Warum muss der Code ungültig sein?

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Xin schrieb:

    Wie lange programmierst Du und was hast Du so programmiert?

    Ich hab im Alter von 11 Jahren angefangen, C++ zu lernen.

    Interessanter Zeitpunkt, dann verstehst Du Programmiersprachen vermutlich anders als die meisten hier.

    Fang mal an und halt mich auf dem Laufenden. Interessiert mich. Wenn Du Fragen hast, auf unten genanntem Forum lese ich alle Boards und bin unter UsernameHier at proggen.org zu erreichen, hier lese eigentlich nur "Beruf und Ausbildung" und ansonsten nur wenn ich Zeit habe und/oder zufällig was Interessantes mitbekomme.

    Mich würde interessieren, was du mit "anders" meinst 😉

    Ich habe mit 9 Jahren angefangen zu programmieren, mit etwa 13 habe ich angefangen Assembler zu programmieren. C und C++ waren damals auf den Computern, die ich mir leisten konnte, nicht verfügbar.

    "Digital Natives", so nennt man das heute so schön, sind bei Programmiersprachen doch eher selten.

    Ich vermute, wer eine Programmiersprache nicht als "Zweitsprache" erlernt, geht damit anders um, als jemand, der eine Sprache eher anhand ihrer Regeln gelernt hat.

    Kellerautomat schrieb:

    Edit: Bist du eigtl. evtl. irgendwo im IRC unterwegs?

    Nicht mehr.

    Entweder stehst Du gerade vor mir oder ich habe keine Zeit für Live-Kommunikation. 😉

    @krümelkacker: Schöne Punkte. 🙂

    knivil schrieb:

    @Xin: Natuerlich ist das Kritik. Ich verstehe sie nicht als destruktiv. Wenn du sie nicht hoeren moechtest, dann unterhaltet euch doch privat, weil das Forum oeffentlich ist.

    Lieber knivil, richte Dir doch bitte ein Forum ein, in dem nur öffentlich besprochen wird, was Dich persönlich interessiert. Ich verspreche, ich komme dann auch nicht vorbei. Wenn Du konstruktive Beiträge zum Thema hast, bitte. Wenn Du Kritik an mir hast, dann ist dieser Thread nicht der passende Bereich. Schreib mir privat, schreib an den Board-Moderator oder akzeptiere, dass dieses Thema nunmal nicht für Dich interessant ist und schweige, wie du es ja eigentlich von selbst aus tun wolltest.



  • Xin schrieb:

    Kellerautomat schrieb:

    Und wie behandelst du dann Fehler, bei denen am Zeitpunkt des Auftretens nicht bekannt ist, wie sie behandelt werden sollen? Gerade das macht Exceptions doch so praktisch.

    Vereinfach ausgedrückt: "false".

    Fehlerbehandlung über Rückgabewerte funktioniert, allerdings braucht man dazu Sum-Typen, sonst hat man ein Problem, wenn die Funktion noch einen normalen Rückgabewert hat. Und damit man dann nicht ständig die Rückgabewerte prüfen muss, braucht man auch noch Monaden bzw. Unterstützung für monadische Strukturen.

    Tatsächlich sind insbesondere Sum-Typen in Verbindung mit Pattern Matching eines der Dinge, die mir bei vielen Sprachen, auch C++, fehlen.



  • krümelkacker schrieb:

    Ich hätte gern implicit statt explicit .

    Über Konvertierungen habe ich noch nicht nachgedacht. Aber du hast Recht, ich fände implicit auch besser.

    krümelkacker schrieb:

    Ich hätte gerne ein C++ ohne diesen ganzen Initialisierungs-Blödsinn (default, value, zero, direct, copy, direct-list, copy-list, ...).

    Hehe. Bei mir gibt es nur 2 Arten von Initialisierung: void-Initialisierung und "normale" Initialisierung. Eine void-Initialisierung entspricht einer uninitialisierten Variable. Wer das möchte, muss es explizit hinschreiben:

    let int x := void // Uninitialisierter int
    

    Und dann eben "normale" Initialisierung, wie ich schon vorher gezeigt habe.

    krümelkacker schrieb:

    Ich wünschte, der ganze Metaprogrammierungskram wäre leichter. Da muss man ja echt krasse Verreknungen machen teilweise. Otto Normalo wird sich daran nicht stören, aber einen Entwickler generischer Bibliotheken nervt das sicher.

    Idealerweise kann man Code sowohl zur Lauzeit, als auch zur Compilezeit ausführen, ohne Änderungen. Details habe ich mir aber noch nicht überlegt.

    krümelkacker schrieb:

    Ich hätte gerne einen Debug-Modus, der mir so etwas wie einen weak_ptr für einen unique_ptr geben kann, der dann automatisch 0 wird, wenn der unique_ptr das Ding löscht und dann bei Dereferenzierung in so einem Zustand den Debugger aktiviert. Nur zwecks Fehlererkennung. Im Release-Mode könnte das ja wieder ein raw-Pointer sein. So könnte man noch dangling-pointer-Probleme in Verbindung mit unique_ptr früher erkennen.

    Das ist wohl ein Library-Feature bzw Teil der Implementierung.

    krümelkacker schrieb:

    Ich hätte gern ein vernünftiges Modul-System, ohne diesen ganzen Header-Krampf. Dann kann man vielleicht auch wieder eine using-Direktive verwenden, weil der using namespace-Kram dann ja nicht in die Schnittstellen-Datei exportiert werden muss.

    Wird wohl bei mir ähnlich wie in Java aussehen. Die Zuteilung Klassenname <-> Dateiname wird es aber nicht geben, da ein Modul keine Klassen beinhalten muss. Weiters wird man importierte Symbole "umbenennen" können, falls mehrere importierte Module ein gleichnamiges Symbol definieren.

    module MyModule
    
    // komplettes Modul importieren
    import CoolModule CM
    import OtherCoolModule OCM
    
    // oder auch
    from CoolModule CM import f
    from OtherCoolModule OCM import f
    
    void f()
    {
        CM.f();
        OCM.f();
    }
    

    krümelkacker schrieb:

    Ich hätte gerne concepts light2.

    Damit meinst du was?

    krümelkacker schrieb:

    Ich hätte gern eine bessere Tuple-Unterstützung. Zum Beispiel so etwas wie folgende syntaktische Abkürzung

    tuple<int,int&,double&> get_stuff();
    
    int main() {
      {a,b,const& c} = get_stuff();
      cout << a;
      cout << b;
      cout << c;
    }
    

    für so etwas:

    tuple<int,int&,double&> get_stuff();
    
    int main() {
      tuple<int,int,const double&> abc = get_stuff();
      cout << std::get<0>(abc);
      cout << std::get<1>(abc);
      cout << std::get<2>(abc);
    }
    

    Etwas ähnliches gibt es schon: std::tie.
    Mit Pattern Matching liesse sich das in meiner Sprache so schreiben:

    let { a, b, c } := get_stuff()
    

    Allerdings weiss ich noch nicht, ob ich Referenzen auf Primitive einführe. const wird es aber definitiv geben.

    krümelkacker schrieb:

    Ich hätte gern ein ordentliches Range-System (das kann man noch auf Bibliotheksbasis machen), so dass man z.b. so Dinge schreiben kann wie

    iunt main()
    {
      vector<double> foo = get_other_stuff();
      for ({index,&value} : indexed(foo)) {
        cout << index << " --> " << value << endl;
      }
    }
    

    wobei es dann noch viele schöne andere "Range-Adapter" gibt...

    Was die stdlib angeht, habe ich zwar Ranges geplant, aber noch keine Details. Vermutich würde ich Indizierung in die Sprache einbauen:

    let foo := get_other_stuff()
    
    foreach(i, value in foo)
        printfln("% --> %", i, value)
    

    rapso schrieb:

    1. wenn schon accessor als sauberer stil angepriesen sind, dann sollte man sie effizienter definieren koennen bis man mehr als den reinen access braucht z.b.

    class foo
    {
    int m_Bar:bar;
    };
    
    ...
    foo F;
    F.bar(1);
    return F.bar();
    

    Ich finde eher, es sollte in Richtung C# Properties gehen.

    rapso schrieb:

    ich wuerde gerne typesafe enum operatoren haben, wenn es sowas wie

    enum EFlags;
    {
     EF_01= 1<<0,
     EF_10= 1<<1,
     EF_11= 3,//EF_01|EF_10
    };
    

    gibt, sollte man auch sowas schreiben koennen

    EFlags Flags(){return EF_01|EF_10;}
    

    zZ hat man ugly type cast falls man das moechte oder man muss auf einen generischen typ umsteigen und verliert damit die 'safety' was gerade bei funktionsueberladen schiefgehen kann.

    Flux(EF_10);
    Flux(EF_01|EF_10);
    

    Sowas nenne ich "flags enum":

    enum flags X
    {
        foo,
        bar,
        baz = foo | bar
    }
    

    rapso schrieb:

    im allgemeinen wuerde ich mir wuenschen mehr features zu haben die schnelleres coden erlauben, ich fuehle mich irgendwie staendig ineffizient. (weil mein kopf 10mal mehr generiert als ich hinklatschen kann).

    Wenn du konkrete Vorschläge hast, bin ich immer dankbar 😉

    @Xin + ipsec: Ich habe leider im Moment wenig Zeit, werde auf eure Posts später eingehen. Habe sie aber nicht vergessen.



  • Kellerautomat schrieb:

    Was die stdlib angeht, habe ich zwar Ranges geplant, aber noch keine Details. Vermutich würde ich Indizierung in die Sprache einbauen:

    let foo := get_other_stuff()
    
    foreach(i, value in foo)
        printfln("% --> %", i, value)
    

    Genau das ist mein Problem mit aktuellen Programmiersprachen: Sie haben viel zu viele Sprachfeatures. Eine Sprache muss schlank und erweiterbar sein und Features sollen von der Library implementiert werden, wenn möglich.

    Es gibt sehr viele Features, die ganz nett wären. Und das schlimmste, was man machen kann, ist diese als Special Case in die Sprachdefinition einbauen.

    Gerade das Indexproblem wäre einfach zu lösen: foo gibt einen Typ zurück, der sich in ein 1-Tupel (Nur Wert) oder 2-Tupel (Index+Wert) umwandeln lässt. Wenn foo eine Map wäre, wären das 1-Tupel (Key-Value-Pair), 2-Tupel (Index + Key-Value-Pair) und 3-Tupel (Index + Key + Value).

    Viele der Features, die du genannt hast, lassen sich verallgemeinern und zusammenmergen.

    Beispiel:

    let int x := void
    

    Wird nur sehr selten gebraucht und wenn, dann lässt sich das durch einen uninitialized<int> lösen, der intern aligned_storage<int> (was sowieso benötigt wird) verwendet. Gewinn: Einfachere Sprache, weniger Fehlerquellen.



  • Kellerautomat schrieb:

    Eine void-Initialisierung entspricht einer uninitialisierten Variable. Wer das möchte, muss es explizit hinschreiben:

    let int x := void // Uninitialisierter int
    

    Und dann eben "normale" Initialisierung, wie ich schon vorher gezeigt habe.

    Interessante Schreibweise... bei mir heißt es "uninitialized int", wobei das uninitialized nur bei Klassen interessant ist, deren Konstruktor ich noch nicht rufen möchte. Es ist nicht verpflichtend und bei int sinnfrei.
    Aber die Idee mit void hat was... nicht, dass mir das void gefiel, aber die zwangsweise "Initialisierung" gefällt mir.

    Kellerautomat schrieb:

    Idealerweise kann man Code sowohl zur Lauzeit, als auch zur Compilezeit ausführen, ohne Änderungen. Details habe ich mir aber noch nicht überlegt.

    AST-Interpreter.

    Ist lustig, wenn man Goto implementiert. 😃

    Kellerautomat schrieb:

    krümelkacker schrieb:

    Ich hätte gerne einen Debug-Modus, der mir so etwas wie einen weak_ptr für einen unique_ptr geben kann, der dann automatisch 0 wird, wenn der unique_ptr das Ding löscht und dann bei Dereferenzierung in so einem Zustand den Debugger aktiviert. Nur zwecks Fehlererkennung. Im Release-Mode könnte das ja wieder ein raw-Pointer sein. So könnte man noch dangling-pointer-Probleme in Verbindung mit unique_ptr früher erkennen.

    Das ist wohl ein Library-Feature bzw Teil der Implementierung.

    Bei unique_ptr vielliecht.

    Kellerautomat schrieb:

    Allerdings weiss ich noch nicht, ob ich Referenzen auf Primitive einführe. const wird es aber definitiv geben.

    Beschäftige Dich mit der Frage, inwieweit Du Primitive einführst.

    Kellerautomat schrieb:

    Sowas nenne ich "flags enum":

    enum flags X
    {
        foo,
        bar,
        baz = foo | bar
    }
    

    Flags sind kein Enums. Darum heißt es bei mir nur flags. Der Rest ist identisch.

    Wenn ich meine Sprache startklar habe, sollten wir uns mal auf eine kühles Getränk treffen und uns über die Syntax austauschen. Mir scheint, wir werden reichlich gemeinsamkeiten finden.

    typique schrieb:

    Genau das ist mein Problem mit aktuellen Programmiersprachen: Sie haben viel zu viele Sprachfeatures. Eine Sprache muss schlank und erweiterbar sein und Features sollen von der Library implementiert werden, wenn möglich.

    Da habe ich genau den entgegengesetzten Ansatz. Ich möchte alles, was regelmäßig gebraucht wird, in der Sprache leicht zugänglich haben.

    Das bedeutet auch, dass sich Sprachen weiterentwickeln, weil die üblichen Ansprüche sich ändern und Sprachen für alte Ansprüche irgendwann unpraktisch werden, um neue zu erfüllen.

    Seien wir ehrlich: C++ ist eine tolle Sprache, sie erfüllt viele Ansprüche, aber im Alltag ist sie unpraktisch, weswegen Sprachen wie C#, Python und sogar PHP erfolgreich wurden. Die Sprachen wurden doch nicht erfolgreich, weil sie besser als C++ sind, sondern nur, weil sie im Alltag praktischer sind.

    typique schrieb:

    Viele der Features, die du genannt hast, lassen sich verallgemeinern und zusammenmergen.

    Beispiel:

    let int x := void
    

    Wird nur sehr selten gebraucht und wenn, dann lässt sich das durch einen uninitialized<int> lösen, der intern aligned_storage<int> (was sowieso benötigt wird) verwendet. Gewinn: Einfachere Sprache, weniger Fehlerquellen.

    Ich finde kellerautomats Syntax lesbarer. Das Problem halte ich für alltäglich.



  • Xin schrieb:

    Kellerautomat schrieb:

    Und wie behandelst du dann Fehler, bei denen am Zeitpunkt des Auftretens nicht bekannt ist, wie sie behandelt werden sollen? Gerade das macht Exceptions doch so praktisch.

    Vereinfach ausgedrückt: "false".

    Wenn die rufende Funktion kein Ergebnis bekommt, kann sie das vielleicht reparieren.

    Wenn ich aber eine Exception schmeiße und 5 Funktionen über mir kommt eine "Division by zero" Exception an, dann kann die damit auch nix mehr anfangen. Ich gehe schön brav Step by Step den Stacktrace hoch. Ein Schritt nach dem anderen, niemals zwei auf einmal. Jeder darf mal kurz überlegen, ob die Rückgabe der Funktion für ihn existenziell wichtig ist, oder ob man da noch was machen kann.

    Dann hast du aber in jeder Funktion Fehlerbehandlungscode, was nicht nur ineffizient, sondern auch unglaublich mühsam ist. Was machst du eigentlich in Konstruktoren?

    Xin schrieb:

    Kellerautomat schrieb:

    Man muss es eben explizit sagen. Selbst wenn eine Funktion in der Basklasse überschreibbar ist, wird sie in einer abgeleiteten Klasse automatisch wieder final.

    Dass man Ableitungen verbietet, mag ich nicht.

    Dass man die Überschreibbarkeit für Ableitungen fordern muss... darüber muss ich nachdenken, ob ich das für eine gute Idee halte.

    Und, hast du dir schon eine Meinung gebildet? 🙂

    Xin schrieb:

    Ein Nullable kann Null sein oder nicht. Ein (int*?) könnte entsprechend Null sein oder nullptr oder irgendwo hin zeigen. Das ist nicht semantisch gleichwertig zu (int *), aber ob es ein Mehrwert ist?
    Bei int, float und enum ist es unbestritten ein Mehrwert - nur halt die Frage, ob das die Implementierung rechtfertigt und damit die Notwendigkeit das Konstrukt zu erlernen.

    Ich bin mir nicht sicher, ob ich einen int*? überhaupt erlauben soll, ähnlich wie man in C++ keinen Zeiger auf eine Referenz erzeugen kann.
    Ich halte die Semantik zumindest für fragwürdig und denke, dass das Verwirrung stiften würde.

    Bei Klassentypen ist Nullable auch nützlich: Man erkennt am Interface, ob eine Funktion null entgegennehmen/zurückgeben kann, oder nicht. Das füehrt dazu, dass viel null-Behandlungscode wegfällt, der in z.B. Java üblich ist.

    Xin schrieb:

    Kellerautomat schrieb:

    Was willst du damit sagen? 😕

    Dass ich es begrüßt hätte, wenn der '='-Operator nicht für als Kopier-Operator missbraucht wird, der von Anfängern dann mit dem '=='-Operator verwechselt wird.

    Ich kann das Problem selbst nicht nachvollziehen. Ich glaube, der Fehler ist mir vor ungefähr einem Monat zum ersten mal passiert, und das war um vier Uhr morgens. Wie wärs mit Haskell-Syntax?

    x <- 42
    

    Xin schrieb:

    Kellerautomat schrieb:

    Nichts, das ist ungültiger Code. In Codezeile 3 hättest du = verwenden müssen.

    Warum unterscheidest Du dann im Quelltext zwischen Zuweisung und Initialisierung?
    Warum muss der Code ungültig sein?

    Weil ich finde, dass eine Unterscheidung zwischen Initialisierung und Zuweisung den Code klarer macht. Und wenn, dann verwende ich sie konsistent.
    Den Vorteil in Konstruktoren habe ich ja schon genannt:

    type Foo
    {
    	// Memberfunktionen und Kon/Destruktoren sind per default public
    	Foo(int x, float y, string z)
    	{
    		// beliebiger Code, member noch nicht initialisiert
    		let temp := someComputation()
    
    		println(.x) // x noch nicht initialisiert, daher read nicht erlaubt --> compiletime error
    
    		.x := x / temp // initialisierung
    
    		if y % 2 == 0
    			.y := 42 // nope, bedingte Initialisierung nicht erlaubt --> compiletime error
    
    		// read von .x okay, wurde initialisiert
    		.z := .x ~ z
    	}
    
    	// Member sind private per default
    	int x
    	float y
    	string z
    }
    

    (Syntax noch nicht fertig)
    Auf Member greift man explizit mit der .member Syntax zu. In C++ gibt es verschiedene Namenskonventionen, etwa m_Member oder member_. Damit baue ich das fest in die Sprache ein.



  • ipsec schrieb:

    Fehlerbehandlung über Rückgabewerte funktioniert, allerdings braucht man dazu Sum-Typen, sonst hat man ein Problem, wenn die Funktion noch einen normalen Rückgabewert hat. Und damit man dann nicht ständig die Rückgabewerte prüfen muss, braucht man auch noch Monaden bzw. Unterstützung für monadische Strukturen.

    Tatsächlich sind insbesondere Sum-Typen in Verbindung mit Pattern Matching eines der Dinge, die mir bei vielen Sprachen, auch C++, fehlen.

    Über Sum-Typen habe ich nachgedacht, aber ich habe noch keine Idee, wie ich sie in die Sprache integrieren würde. Im Moment halte ich auch andere Dinge für wichtiger.
    Von Monaden habe ich leider zu wenig Ahnung, um darüber eine qualifizierte Aussage zu treffen. Ich wollte bei Zeiten mal etwas mehr Haskell lernen.

    typique schrieb:

    Kellerautomat schrieb:

    Was die stdlib angeht, habe ich zwar Ranges geplant, aber noch keine Details. Vermutich würde ich Indizierung in die Sprache einbauen:

    let foo := get_other_stuff()
    
    foreach(i, value in foo)
        printfln("% --> %", i, value)
    

    Genau das ist mein Problem mit aktuellen Programmiersprachen: Sie haben viel zu viele Sprachfeatures. Eine Sprache muss schlank und erweiterbar sein und Features sollen von der Library implementiert werden, wenn möglich.

    Es gibt sehr viele Features, die ganz nett wären. Und das schlimmste, was man machen kann, ist diese als Special Case in die Sprachdefinition einbauen.

    Das sehe ich ähnlich wie Xin. Wichtige Sprachfeatures sollen leicht verfügbar sein.
    Vielleicht helfen da automagisch gemanagte Imports, aber das ist ein IDE-Feature. Nicht jeder verwende eine solche IDE.

    In C++ jedenfalls ist es furchtbar nervig, dass ich für jede Kleinigkeit was includen muss.

    typique schrieb:

    Gerade das Indexproblem wäre einfach zu lösen: foo gibt einen Typ zurück, der sich in ein 1-Tupel (Nur Wert) oder 2-Tupel (Index+Wert) umwandeln lässt. Wenn foo eine Map wäre, wären das 1-Tupel (Key-Value-Pair), 2-Tupel (Index + Key-Value-Pair) und 3-Tupel (Index + Key + Value).

    Wie implementierst du sowas effizient? Warum muss der Index üeberhaupt im Wert enhalten sein? Der kann einfach mitgezählt werden.
    Vielleicht ginge das mit einem Code Generator.

    typique schrieb:

    Viele der Features, die du genannt hast, lassen sich verallgemeinern und zusammenmergen.

    Beispiel:

    let int x := void
    

    Wird nur sehr selten gebraucht und wenn, dann lässt sich das durch einen uninitialized<int> lösen, der intern aligned_storage<int> (was sowieso benötigt wird) verwendet. Gewinn: Einfachere Sprache, weniger Fehlerquellen.

    Wenn ich es brauche, will ich es einfach verfügbar haben.



  • Xin schrieb:

    Kellerautomat schrieb:

    Idealerweise kann man Code sowohl zur Lauzeit, als auch zur Compilezeit ausführen, ohne Änderungen. Details habe ich mir aber noch nicht überlegt.

    AST-Interpreter.

    Ist lustig, wenn man Goto implementiert. 😃

    Also ein Interpreter im Compiler. Und der Interpeter JIT-compiled. Compiler im Interpreter im Compiler? 🤡

    goto werde ich vermutlich nicht einbauen. Habe ich noch nie gebraucht.

    Xin schrieb:

    Kellerautomat schrieb:

    Allerdings weiss ich noch nicht, ob ich Referenzen auf Primitive einführe. const wird es aber definitiv geben.

    Beschäftige Dich mit der Frage, inwieweit Du Primitive einführst.

    Ich hab mir schon lange den Kopf zerbrochen und bin mir immer noch unsicher. Ich bin kein Freund von "alle Typen sind Klassen", genauso will ich aber nicht reine Primitive wie in Java. Beispielsweise macht es Sinn, dass man Objekte der Klasse BigInteger addieren kann, wenn man auch ints addieren kann. Genauso sollte int aber auch statische Properties haben können wie eine Klasse, etwa .SIZE/.ALIGN oder .MIN/.MAX. Irgendwo muss man eine Linie ziehen, die Frage ist nur wo.

    Xin schrieb:

    Kellerautomat schrieb:

    Sowas nenne ich "flags enum":

    enum flags X
    {
        foo,
        bar,
        baz = foo | bar
    }
    

    Flags sind kein Enums. Darum heißt es bei mir nur flags. Der Rest ist identisch.

    Ich hab drüber nachgedacht, ob es enum flags oder nur flags sein soll. Beides sind Aufzählungen an Werten, lediglich verwendet werden sie unterschiedlich. (enum ist ein Oder-Typ, während flags ein Und-Typ ist)
    Letztendlich ists aber nur Syntax.

    Xin schrieb:

    Wenn ich meine Sprache startklar habe, sollten wir uns mal auf eine kühles Getränk treffen und uns über die Syntax austauschen. Mir scheint, wir werden reichlich gemeinsamkeiten finden.

    Du bist Deutscher, oder? Etwas weit weg für mich. 🤡



  • Kellerautomat schrieb:

    Dann hast du aber in jeder Funktion Fehlerbehandlungscode, was nicht nur ineffizient, sondern auch unglaublich mühsam ist. Was machst du eigentlich in Konstruktoren?

    Zum einen ist es nicht ineffizient, denn Du musst so oder so fragen, ob das Ergebnis einen Sinn ergibt, wenn Du - falls Du unzufrieden bist - throw rufen möchtest.

    Außerdem kostet try...catch auch dann, wenn alles super ist.

    Konstruktoren sind bei mir im Prinzip normale Funktionen.

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Man muss es eben explizit sagen. Selbst wenn eine Funktion in der Basklasse überschreibbar ist, wird sie in einer abgeleiteten Klasse automatisch wieder final.

    Dass man Ableitungen verbietet, mag ich nicht.

    Dass man die Überschreibbarkeit für Ableitungen fordern muss... darüber muss ich nachdenken, ob ich das für eine gute Idee halte.

    Und, hast du dir schon eine Meinung gebildet? 🙂

    Nein.

    Kellerautomat schrieb:

    Xin schrieb:

    Ein Nullable kann Null sein oder nicht. Ein (int*?) könnte entsprechend Null sein oder nullptr oder irgendwo hin zeigen. Das ist nicht semantisch gleichwertig zu (int *), aber ob es ein Mehrwert ist?
    Bei int, float und enum ist es unbestritten ein Mehrwert - nur halt die Frage, ob das die Implementierung rechtfertigt und damit die Notwendigkeit das Konstrukt zu erlernen.

    Ich bin mir nicht sicher, ob ich einen int*? überhaupt erlauben soll, ähnlich wie man in C++ keinen Zeiger auf eine Referenz erzeugen kann.
    Ich halte die Semantik zumindest für fragwürdig und denke, dass das Verwirrung stiften würde.

    Dann baust Du Sonderbedingungen ein.

    Kellerautomat schrieb:

    Bei Klassentypen ist Nullable auch nützlich: Man erkennt am Interface, ob eine Funktion null entgegennehmen/zurückgeben kann, oder nicht. Das füehrt dazu, dass viel null-Behandlungscode wegfällt, der in z.B. Java üblich ist.

    Wow, das klingt echt gut!

    In der PM stand 2005 ein Bericht, dass Autos per Funk den Ampeln ein Signal geben können, so dass die Ampeln rechtzeitig auf Grün schalten können, damit man nachts nicht einsam und verlassen an einer roten Ampel stehen müsste. Das ist natürlich Zukunftsmusik bis alle Fahrzeuge mit dem passenden Sender ausgestattet sind und die Ampeln mit dem entsprechenden Empfänger.

    Ein Leserbrief erklärte, dass man die Ampeln auch abschalten kann und sich die Autofahrer an den daran befestigten Schildern über die Vorfahrt informieren könnte.

    Bau Dir kein aufwendige Problemlösung für ein bereits perfekt gelöstes Problem:
    Eine Nullable-Reference heißt in C++ Pointer.

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Was willst du damit sagen? 😕

    Dass ich es begrüßt hätte, wenn der '='-Operator nicht für als Kopier-Operator missbraucht wird, der von Anfängern dann mit dem '=='-Operator verwechselt wird.

    Ich kann das Problem selbst nicht nachvollziehen. Ich glaube, der Fehler ist mir vor ungefähr einem Monat zum ersten mal passiert, und das war um vier Uhr morgens. Wie wärs mit Haskell-Syntax?

    x <- 42
    

    Ich habe bei CSS abgeguckt.

    x: 42;
    

    := ist mir zu lang, dafür wird die Wert-Zuweisung zu oft benutzt.

    Der Vergleich ist ==. Die Gleichsetzung (enums, flags) erfolgt mit =. Grundsätzlich versuche ich den = Operator allerdings eher klein zu halten, weil es die Leute so oder so schon nerven wird, wenn sie Zuweisungen mit = eingeben und dann Fehlermeldungen erhalten. Dann sollten sie wenigstens aussagekräftig und unmissverständlich sein. Das wird schwierig, wenn = im Alltag nicht klar aussortiert werden kann.

    Kellerautomat schrieb:

    Weil ich finde, dass eine Unterscheidung zwischen Initialisierung und Zuweisung den Code klarer macht. Und wenn, dann verwende ich sie konsistent.

    Eigentlich finde ich den Unterschied als Entwickler uninteressant.
    Es wäre nur schön, wenn das Objekt existiert, wenn ich es erstmal nutze.

    Kellerautomat schrieb:

    if y % 2 == 0
    			.y := 42 // nope, bedingte Initialisierung nicht erlaubt --> compiletime error
    

    Genau das will ich haben. Bedingte Initialisierung.

    Kellerautomat schrieb:

    Auf Member greift man explizit mit der .member Syntax zu. In C++ gibt es verschiedene Namenskonventionen, etwa m_Member oder member_. Damit baue ich das fest in die Sprache ein.

    Hehehe, hast Du Zugriff auf mein Wiki? 😃
    Das habe ich mir bei Visual Basic abgeguckt, wenn ich mich recht entsinne.

    Du benutzt diese .member Syntax hier bei Funktionen?



  • Ich verstehe das Argument gegen Exceptions nicht. Warum sind Exceptions schlecht nur weil der Programmierer zu faul ist sie am richtigen Ort zu behandeln?
    Aber lieber ne Exception die bis zu main durchfliegt als ignorierte Fehlerbehandlung in den tieferen Funktionen was zu komplett undefinierten Verhaltens des Programm führt.
    Lieber ne ConfigNotFound Exception als ein Programm das am Rad dreht weil es meint ne Config eingelesen zu haben obwohl gar keine da war.



  • Kellerautomat schrieb:

    krümelkacker schrieb:

    Ich hätte gerne concepts light2.

    Damit meinst du was?

    ein ausgefeiltes concepts light 🙂
    wie auch immer das dann aussehen wird.

    Kellerautomat schrieb:

    krümelkacker schrieb:

    Ich hätte gern eine bessere Tuple-Unterstützung. Zum Beispiel so etwas wie folgende syntaktische Abkürzung

    tuple<int,int&,double&> get_stuff();
    
    int main() {
      {a,b,const& c} = get_stuff();
      cout << a;
      cout << b;
      cout << c;
    }
    

    für so etwas:

    tuple<int,int&,double&> get_stuff();
    
    int main() {
      tuple<int,int,const double&> abc = get_stuff();
      cout << std::get<0>(abc);
      cout << std::get<1>(abc);
      cout << std::get<2>(abc);
    }
    

    Etwas ähnliches gibt es schon: std::tie.
    Mit Pattern Matching liesse sich das in meiner Sprache so schreiben:

    let { a, b, c } := get_stuff()
    

    Allerdings weiss ich noch nicht, ob ich Referenzen auf Primitive einführe. const wird es aber definitiv geben.

    std::tie ist mir bekannt. Du musst die Variablen allerdings vorher definieren, ggf initialisieren (über tie bekommst du nur eine Zuweisung) und hast damit auch keine Typdeduktion mehr.



  • Ethon schrieb:

    Ich verstehe das Argument gegen Exceptions nicht. Warum sind Exceptions schlecht nur weil der Programmierer zu faul ist sie am richtigen Ort zu behandeln?
    Aber lieber ne Exception die bis zu main durchfliegt als ignorierte Fehlerbehandlung in den tieferen Funktionen was zu komplett undefinierten Verhaltens des Programm führt.
    Lieber ne ConfigNotFound Exception als ein Programm das am Rad dreht weil es meint ne Config eingelesen zu haben obwohl gar keine da war.

    Die Frage geht wohl an mich.

    Im Grundgedanken liegst Du richtig. Exceptions sind ja auch nicht von dummen Leuten ohne Grund eingeführt worden. Genau das ist das Konzept.

    Es gibt zwei Strategien Sprachfeatures (das gilt also nicht nur für Exceptions) zu bewerten: Das Sprachfeature ist gut, wenn sich der Mensch daran diszipliniert halten würde und das Sprachfeature ist schlecht, weil der Mensch der Mensch sich nicht diszipliniert daran halten kann.

    Exceptions haben nur dann einen Vorteil, wenn sie behandelt werden. Als Entwickler willst Du aber oftmals erstmal Deinen Algorithmus laufen haben, so dass sich unter try ein catch-All wiederfindet. Wenn Du mal professionell entwickelt hast, wirst Du weiterhin mal erlebt haben, dass laufend ein Projektmanager in der Tür steht und fragt, ob Du jetzt fertig bist und Dir neue Aufgaben gibt. Außerdem ist die aktuelle gerade nicht so wichtig und eigentlich ja auch fertig - läuft ja.

    Du wirst in dem Code also reichlich Catch-All finden - die Exception fliegt also nicht zu main durch. Damit sind sämtliche Fehlerindizien weg. Aber das Programm eiert nun genauso rum, wie eine nicht abgefangene Fehlerbehandlung. Du erhältst also irgendwo eine valide Exception, die Dich darauf hinweist (idealerweise kommt wenigstens die zweite durch, aber muss ja nicht). Das ganze wird also recht willkürlich, weil die Exception in einem Bereich auftritt, der mit (eigentlich) validen Daten gefüttert wird auch perfekt getestet ist. Das Catch-All verheimlicht aber, dass es einen Fall gibt, wo die Daten nicht valide sind. Das findet kein Mensch mehr, besonders, wenn erst die dritte oder vierte Exception in der Reihe zum User durchdringt.

    Debuggt man durch Exceptions findet man sich im Schadensfall nicht mehr an der Stelle, wo der Fehler passiert ist. Der Stackframe ist weg. Eventuell löst der Abbau des Stackframes auch neue, andere Exceptions aus.

    Auch die Catch-Anweisung ist fehleranfällig. Gerne werden Exceptions voneinander abgeleitet. Ein Catch behandelt dann auf einmal eine Oberklasse eines Fehlers, ist aber auf die Spezialisierung gar nicht ausgelegt. Der Fehler wird behandelt, aber eben nicht korrekt. Eclipse fördert das auch, in dem es vorschlägt, statt mehrerer Spezialisierungen diese durch die Oberklasse zusammen zu fassen. Bei catch kommt dann nur noch "allgemeiner Programmfehler" an, der muss behandelt werden und das tun die Leute auch. Nur weiß halt keiner, was er eigentlich behandeln müsste und schon haben wir wieder ein Catch-All, weil der Algorithmus muss fertig werden.
    Exceptions waren in meiner bisherigen Karriere für mehr Fehler verantwortlich als die eigentlichen Algorithmen, deren Fehlerbehandlung sie übernehmen sollten.

    Wenn man nun erklärt, dass der Programmierer schuld ist, weil er Exceptions falsch benutzt, dann ist das korrekt. Meiner Erfahrung nach ist die Fehlerbehandung mit Exceptions in größeren Projekten nicht mehr möglich, weil sie sich über das ganze Programm verstreut, statt sich in dem kleinen Rahmen abzuspielen, der für genau einen Funktionsaufruf gilt. Hier kann ich diszipliniert arbeiten.

    Ich gehe den zweiten Ansatz. Der Programmierer kann ein großes Projekt nicht überblicken, also muss ich Konstrukte vermeiden, die ihm zu Fehlern drängen. Exceptions gehören für mich dazu.



  • Xin schrieb:

    Kellerautomat schrieb:

    Dann hast du aber in jeder Funktion Fehlerbehandlungscode, was nicht nur ineffizient, sondern auch unglaublich mühsam ist. Was machst du eigentlich in Konstruktoren?

    Zum einen ist es nicht ineffizient, denn Du musst so oder so fragen, ob das Ergebnis einen Sinn ergibt, wenn Du - falls Du unzufrieden bist - throw rufen möchtest.

    Außerdem kostet try...catch auch dann, wenn alles super ist.

    Wenn ich davon ausgehen kann, dass im Fehlerfall eine Exception geworfen wird, muss ich gar nichts prüfen.
    Bei einem ordentlichen Compiler kosten Exceptions gar nichts, solange sie nicht geworfen werden. Siehe http://lazarenko.me/2011/07/22/c-exception-handling-and-performance/.

    Xin schrieb:

    Konstruktoren sind bei mir im Prinzip normale Funktionen.

    Das heisst, sie können etwas zurückgeben? Ruft man die explizit auf?

    Xin schrieb:

    Kellerautomat schrieb:

    Ich bin mir nicht sicher, ob ich einen int*? überhaupt erlauben soll, ähnlich wie man in C++ keinen Zeiger auf eine Referenz erzeugen kann.
    Ich halte die Semantik zumindest für fragwürdig und denke, dass das Verwirrung stiften würde.

    Dann baust Du Sonderbedingungen ein.

    Nur weil ich definiere, was int*? bedeutet, wird das Konstrukt nicht besser. Wenn sich jedes Mal jemand fragt, was die genaue Semantik davon ist, ist es besser, es gleich nicht zu erlauben.

    Xin schrieb:

    Kellerautomat schrieb:

    Bei Klassentypen ist Nullable auch nützlich: Man erkennt am Interface, ob eine Funktion null entgegennehmen/zurückgeben kann, oder nicht. Das füehrt dazu, dass viel null-Behandlungscode wegfällt, der in z.B. Java üblich ist.

    Wow, das klingt echt gut!

    In der PM stand 2005 ein Bericht, dass Autos per Funk den Ampeln ein Signal geben können, so dass die Ampeln rechtzeitig auf Grün schalten können, damit man nachts nicht einsam und verlassen an einer roten Ampel stehen müsste. Das ist natürlich Zukunftsmusik bis alle Fahrzeuge mit dem passenden Sender ausgestattet sind und die Ampeln mit dem entsprechenden Empfänger.

    Ein Leserbrief erklärte, dass man die Ampeln auch abschalten kann und sich die Autofahrer an den daran befestigten Schildern über die Vorfahrt informieren könnte.

    Bau Dir kein aufwendige Problemlösung für ein bereits perfekt gelöstes Problem:
    Eine Nullable-Reference heißt in C++ Pointer.

    Ich kann deiner Analogie leider nicht folgen.
    Pointer möchte ich, soweit möglich, ersetzen. Nicht, dass ich Pointer für schlecht halte, aber es gibt für die meisten Fälle sicherere Alternativen. Zumal Objekte, wie schon gesagt, per default Reference-Counted sind - ein roher Pointer hat da nicht die richtigen Kopiersemantiken.

    Und nur, um es nochmal hervorzuheben: Eine Referenz ist bei mir - anders als in Java - NICHT nullable. T? ist das, was T in Java ist - mit dem Unterschied, dass es auch mit Primitiven funktioniert.

    Xin schrieb:

    Ich habe bei CSS abgeguckt.

    x: 42;
    

    := ist mir zu lang, dafür wird die Wert-Zuweisung zu oft benutzt.

    Der Vergleich ist ==. Die Gleichsetzung (enums, flags) erfolgt mit =. Grundsätzlich versuche ich den = Operator allerdings eher klein zu halten, weil es die Leute so oder so schon nerven wird, wenn sie Zuweisungen mit = eingeben und dann Fehlermeldungen erhalten. Dann sollten sie wenigstens aussagekräftig und unmissverständlich sein. Das wird schwierig, wenn = im Alltag nicht klar aussortiert werden kann.

    Gefällt mir persönlich überhaupt nicht. Ist halt Geschmackssache.

    Xin schrieb:

    Kellerautomat schrieb:

    Weil ich finde, dass eine Unterscheidung zwischen Initialisierung und Zuweisung den Code klarer macht. Und wenn, dann verwende ich sie konsistent.

    Eigentlich finde ich den Unterschied als Entwickler uninteressant.
    Es wäre nur schön, wenn das Objekt existiert, wenn ich es erstmal nutze.

    Kellerautomat schrieb:

    if y % 2 == 0
    			.y := 42 // nope, bedingte Initialisierung nicht erlaubt --> compiletime error
    

    Genau das will ich haben. Bedingte Initialisierung.

    Das glaube ich nicht. Ich glaube eher, du willst abhängig von einer Bedingung mit einem von zwei Werten initialisieren. Das kannst du mit einer temporären Variable oder ?:.

    Xin schrieb:

    Kellerautomat schrieb:

    Auf Member greift man explizit mit der .member Syntax zu. In C++ gibt es verschiedene Namenskonventionen, etwa m_Member oder member_. Damit baue ich das fest in die Sprache ein.

    Hehehe, hast Du Zugriff auf mein Wiki? 😃
    Das habe ich mir bei Visual Basic abgeguckt, wenn ich mich recht entsinne.

    Du benutzt diese .member Syntax hier bei Funktionen?

    VB kann ich nicht mal, nie gelernt. Ich kam von selbst auf die idee :p

    .member lässt sich innerhalb von Methoden, Konstruktoren und Destruktoren verwenden. Bzw., es muss verwendet werden.



  • Bezüglich Exceptions:

    Ich verstehe das Beispiel mit loadConfig . Wenn die Datei nicht da ist, muss sich die Funktion darum kümmern, irgendwo anders die Config herzubekommen, oder eine neue anzulegen, oder ähnliches. Es wäre falsch, wenn die Exception einfach durchrauscht. Aber loadConfig ruft getFileContents auf und das wiederum openFile . Und was soll nun getFileContents machen, wenn openFile einen Fehler erzeugt? Es kann diesen nur weiter geben, es ist gar nicht in der Position, selbstständig Entscheidungen zu treffen.

    In der Regel gibt es sehr viele dieser Hilfsfunktionen. Und in diesen ist das Standardmuster meistens das folgende:

    func1();
    if(fehler) return fehler;
    func2();
    if(fehler) return fehler;
    usw.
    

    Exception bieten genau das als Voreinstellung, auch über mehrere Ebenen hinweg. Und auf den oberen Ebenen können die Funktionen, die die Anwendungslogik abbilden (z.B. loadConfig ) die Exceptions fangen und basierend darauf Entscheidungen treffen, wie zu verfahren ist.

    Fehlerbehandlung über Rückgabewerte (wie z.B. in Go) auf der anderen Seite erzwingt den Programmierer, die if -Aufrufe aus dem Pseudocode oben immer explizit zu schreiben. Laut meiner Erfahrung macht man das in der Regel nicht, insbesondere, wenn der Fehler unwahrscheinlich ist. Standardbeispiel: wer prüft den Rückgabewert von printf ? Gerade wenn man den Algorithmus erstmal laufen haben will, lässt man die Fehlerbehandlung gerne weg. Das ist hier aber drastischer als im Exception-Fall. Wenn ein Fehler auftritt, ist der Programmzustand in der Regel inkonsistent. Exceptions brechen dann ab und führen das Programm erst dann weiter, wenn jemand meint, sich um den Fehler kümmern zu können. Der Fehler-Rückgabewert wird aber ignoriert und die Programm arbeitet auf einen inkonsistenten Zustand weiter, der Bug taucht dann in der Regel an einer gänzlich anderen Stelle auf.

    Fehler über Rückgabewerte haben den Vorteil, dass der Fehler nicht durchs ganze Programm fliegt, aber sie führen zu verschleppten Bugs und außerdem zu unhandlicherem Code. Aus

    a();
    b();
    c();
    

    wird

    if(err = a()) return err;
    if(err = b()) return err;
    if(err = c()) return err;
    

    Aus

    return parseConfig(readFileContents(filename))
    

    wird

    content, err = readFileContents(filename);
    if(err) return nil, err;
    conf, err = parseConfig(content);
    if(err) return nil, err;
    return conf, nil;
    

    Die Fehlerbehandlung wird monoton und man fragt sich, ob einem das nicht abgenommen werden kann.

    Im Prinzip kann man die Vorteile vereinen und sowohl lokale Fehler und compilergeprüfte Fehlerbehandlung als auch elegenten Code, der nicht mit Fehlerprüfung durchsetzt ist, haben. Wie gesagt durch Sum-Typen und Monaden. Ich kenne keinen anderen Weg, lasse mich aber auch gerne eines besseren belehren. Insbesondere, Xin, würde mich interessieren, wie du dir das vorgestellt hast und wie du die Probleme umgehen möchtest.



  • Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Dann hast du aber in jeder Funktion Fehlerbehandlungscode, was nicht nur ineffizient, sondern auch unglaublich mühsam ist. Was machst du eigentlich in Konstruktoren?

    Zum einen ist es nicht ineffizient, denn Du musst so oder so fragen, ob das Ergebnis einen Sinn ergibt, wenn Du - falls Du unzufrieden bist - throw rufen möchtest.

    Außerdem kostet try...catch auch dann, wenn alles super ist.

    Wenn ich davon ausgehen kann, dass im Fehlerfall eine Exception geworfen wird, muss ich gar nichts prüfen.
    Bei einem ordentlichen Compiler kosten Exceptions gar nichts, solange sie nicht geworfen werden. Siehe http://lazarenko.me/2011/07/22/c-exception-handling-and-performance/.

    Zwei Kommentare dazu:

    1. try und die } nach try kosten Zeit, denn es muss festgelegt werden wo throw im Fehlerfall hinspringen muss und wenn } erreicht wird, muss die Änderung zurückgenommen werden, damit zum übergeordneten catch-Block gesprungen wird. Scott Meyer beschreibt das in Effektive C++, keine Ahnung in welchem Band.
      Wenn Du dir Gedanken darüber machst, wie Du eine Sprache implementierst, muss Dir klar sein, dass Du in einem try anders agierst als außerhalb und diese Änderung musst Du im Programm vermerken und das gibt's nunmal nicht kostenlos.

    2. Das Beispiel mit den zwei Divisionen ist semantisch nicht gleich, entsprechend auch nicht vergleichbar. Es ist vergleichbar unter der Annahme, dass der konstruierte Fall geeignet wäre, einen objektiven Vergleich zu gestalten. Das Exception-Beispiel entspricht nicht dem Beispiel mit dem Rückgabecode, sondern nur dann, wenn der erste Aufruf von divide die gleiche Fehlerbehandlung benötigt, wie der zweite.

    Erzeugt die erste Division eine Statusänderung im Programm, so muss die Fehlerbehandlung der zweiten Division das berücksichtigen. Das ganze funktioniert mit zwei divide()-Funktionen, so dass es gut aussieht. Ich mache keine gut aussehenden Features. Ich will praktische Features.

    Musst Du die Statusänderung zurücknehmen, zerpflückt sich Deine Fehlerbehandlung. Entweder beginnst Du mit temporären Variablen zu jonglieren oder Du baust ganz sauber um beide divides getrennte try-Catch-Blöcke packen. Viel Aufwand, nur um einen Rückgabecode zu prüfen.

    Ansonsten reicht für das Beispiel if( divide(..) && divide(..) ) {} else { einmalige Fehlerbehandlung. }

    Der Artikel hat sich einen Show-Case rausgesucht in dem mehrere Annahmen implizit so getroffen werden, dass try-catch sinnvoll erscheint. Ich treffe meine Entscheidungen lieber aus Schwächen in Produktivcode. Meine Erfahrung und darauf aufbauend meine Entscheidung ist, Ausnahmen regelmäßig zu verwenden, ist ziemlicher Mist.

    Ich gebe zu, den Rest habe ich mir nicht mehr angeguckt. Die Argumentation hatte ich schon etliche Male und da der Autor mit dem gleichen Blödsinn eröffnet erwarte ich später nichts Neues.

    Kellerautomat schrieb:

    Xin schrieb:

    Konstruktoren sind bei mir im Prinzip normale Funktionen.

    Das heisst, sie können etwas zurückgeben? Ruft man die explizit auf?

    Die Möglichkeit gibt es.

    Bzgl. des Aufrufs spiele ich noch mit einer Reihe von Möglichkeiten.

    Kellerautomat schrieb:

    Nur weil ich definiere, was int*? bedeutet, wird das Konstrukt nicht besser. Wenn sich jedes Mal jemand fragt, was die genaue Semantik davon ist, ist es besser, es gleich nicht zu erlauben.

    Das sehe ich anders, hier käme Willkür ins Spiel. Aber da ich kein int*? in der Semantik habe, also auch kein int?, stellt sich die Frage für mich nicht.

    Im Gegensatz zu uninitialized<int> sehe ich nullable<int> tatsächlich als Library-Feature, denn für alle Datentypen lässt sich das notfalls über einen Zeiger ausdrücken, der immer Nullable ist. In C++: int const *: "Da steht das gewünschte int - oder es gibt keins."
    Das ist bei int nicht so schön, wie int?, aber funktioniert für alle KlassenObjekte absolut in Ordnung. Für den seltenen Sonderfall der Primitive baue ich kein eigenes Konstrukt.
    Die Notwendigkeit für int? sehe ich nicht.

    Aber sollten unsere Sprachen weiterhin so große Ähnlichkeit aufweisen, haben wir schonmal einen unbedeutenden Unterschied 😉

    Kellerautomat schrieb:

    Xin schrieb:

    Kellerautomat schrieb:

    Bei Klassentypen ist Nullable auch nützlich: Man erkennt am Interface, ob eine Funktion null entgegennehmen/zurückgeben kann, oder nicht. Das füehrt dazu, dass viel null-Behandlungscode wegfällt, der in z.B. Java üblich ist.

    Wow, das klingt echt gut!

    In der PM stand 2005 ein Bericht, dass Autos per Funk den Ampeln ein Signal geben können, so dass die Ampeln rechtzeitig auf Grün schalten können, damit man nachts nicht einsam und verlassen an einer roten Ampel stehen müsste. Das ist natürlich Zukunftsmusik bis alle Fahrzeuge mit dem passenden Sender ausgestattet sind und die Ampeln rmit dem entsprechenden Empfänger.

    Ein Leserbrief erklärte, dass man die Ampeln auch abschalten kann und sich die Autofahrer an den daran befestigten Schildern über die Vorfahrt informieren könnte.

    Bau Dir kein aufwendige Problemlösung für ein bereits perfekt gelöstes Problem:
    Eine Nullable-Reference heißt in C++ Pointer.

    Ich kann deiner Analogie leider nicht folgen.
    Pointer möchte ich, soweit möglich, ersetzen. Nicht, dass ich Pointer für schlecht halte, aber es gibt für die meisten Fälle sicherere Alternativen. Zumal Objekte, wie schon gesagt, per default Reference-Counted sind - ein roher Pointer hat da nicht die richtigen Kopiersemantiken.

    Hehehe, Du bist der Sprachdesigner. Du entscheidest welche Semantik ein Pointer hat.

    Kellerautomat schrieb:

    Und nur, um es nochmal hervorzuheben: Eine Referenz ist bei mir - anders als in Java - NICHT nullable. T? ist das, was T in Java ist - mit dem Unterschied, dass es auch mit Primitiven funktioniert.

    T ptr ist bei mir, was in Java T ist, was in C++ T * ist, bei Dir T?.
    T ist bei mir, was in Java nicht geht, was in C++ T &, bei Dir T.
    T copy ist bei mir, was in Java auch nicht geht, in C T und bei Dir...?

    ptr sieht erstmal scheiße aus. Das ist beabsichtig. In 95% kann man das ptr weglassen, also mit Referenzen arbeiten. Und genau das ist ja auch das Ziel. Wo kein Null reingehen kann, kann auch keine Null-Pointer-Exception/SegFault kommen.

    Kellerautomat schrieb:

    Genau das will ich haben. Bedingte Initialisierung.

    Das glaube ich nicht. Ich glaube eher, du willst abhängig von einer Bedingung mit einem von zwei Werten initialisieren. Das kannst du mit einer temporären Variable oder ?:.[/quote]
    Mit einer temporären Variable?

    Natürlich muss das Objekt initialisiert sein, wenn es erstmals initialisiert wird.
    Das muss die semantische Analyse halt erfassen.

    ipsec schrieb:

    Die Fehlerbehandlung wird monoton und man fragt sich, ob einem das nicht abgenommen werden kann.

    🙂



  • Xin schrieb:

    1. try und die } nach try kosten Zeit, denn es muss festgelegt werden wo throw im Fehlerfall hinspringen muss und wenn } erreicht wird, muss die Änderung zurückgenommen werden, damit zum übergeordneten catch-Block gesprungen wird. Scott Meyer beschreibt das in Effektive C++, keine Ahnung in welchem Band.
      Wenn Du dir Gedanken darüber machst, wie Du eine Sprache implementierst, muss Dir klar sein, dass Du in einem try anders agierst als außerhalb und diese Änderung musst Du im Programm vermerken und das gibt's nunmal nicht kostenlos.

    Das ist unter x86 so, unter x64 tun wir eine ip map verwenden. wir erkennen anhand der position des ip welchen zustand wir haben und was wir tun müssen wenn hier eine exception fliegt. kostet nur beim start der anwendung ein paar byte speicher zu laden.

    weiter habe ich dann nicht gelesen :p


Anmelden zum Antworten