[Sprachentwurf] fun pl

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

[Sprachentwurf] fun pl

Beitrag von CodingCat »

Da im Nachbar-Thread Interesse am Entwurf der Sprache angemeldet wurde, die ich vor einem halben Jahr zusammen mit jokester aus dem sppro.de-Forum nebenan angedacht hatte, mache ich mit diesem Post mal einen Anfang, diesen zu skizzieren. Für die Sprache existiert soweit nur ein unfertiger Parser, und es ist fraglich, ob je mehr daraus wird.

Folgende Punkte waren uns besonders wichtig:
  • Die Vereinheitlichung von Methoden und freien Funktionen: Die Verankerung von Methoden in Klassen erschien uns überflüssig; sie führt offensichtlich zu Inkonsistenz beim Aufruf, wenn fremde Module zusätzliche Operationen auf Objekten implementieren. Diese könnten zwar umständlich nachträglich in die Klasse injiziert werden, dies entspricht jedoch in keinster Weise der Kapselung als Grundidee hinter Klassen. Tatsächlich erscheint die traditionelle Umsetzung von Klassen aus meiner heutigen Sicht kein gutes Mittel der Kapselung, weil sie vorschreibt, welche Operationen aus Implementierungsgründen objekteigen sind, und welche sich alleine auf der Objektschnittstelle implementieren lassen.
  • Ein nicht textbasiertes Modulsystem: Hierzu muss ich vermutlich nicht viel sagen, dass Text-#includes suboptimal bis nicht zu gebrauchen sind, hat wohl jeder hier zur Genüge selbst erfahren.
  • Verbesserte Templates: Templates in C++ sind umständlich zu schreiben und erlauben ohne vollständige Instantiierung nur eine sehr eingeschränkte statische Prüfung. Concepts retten uns hoffentlich mit dem nächsten C++-Standard aus dieser misslichen Lage.
  • Einfache Übersetzbarkeit: Die Grammatik haben wir nie formalisiert, sie existiert nur implizit im Parser-Prototyp. Das geht, weil zu jedem Zeitpunkt klar ist, welches Token folgen muss. Damit einher geht auch der nächste Punkt ...
  • Trennung von Typ- und Werte-Kontexten: Um im Rahmen eines bequemen Modulsystems ohne jegliches Vorwissen durch Deklarationen vollständig übersetzen zu können, ist an jeder Stelle klar, ob ein Typ oder ein Wert gemeint ist. Damit erübrigen sich auch Mehrdeutigkeiten bei gleichnamigem Objekt und Objekttyp.
  • Deterministische logische Objektkonstruktion und -destruktion: RAII funktioniert in C++ so gut, dass wir dem erstmal nichts hinzuzufügen hatten.
  • Ausnahmebehandlung ebenso, leicht ergänzt.
Variablen und Typen

Code: Alles auswählen

def a = 2 // Konstante vom Typ int
def b : int = 2 // Konstante vom Typ int mit expliziter Typangabe
var c : string // String-Variable
var d : float = uninitialized // Uninitialisierte float-Variable
d = b -> float // b zu float gecastet
Hier wird bereits deutlich, wie Wert- und Typ-Kontext strikt getrennt sind. Die Trennung von def und var hat den Hintergedanken, dass die Definition von Konstanten mit def in keiner Weise umständlicher als die Definition von Variablen gemacht wird, und so nach Möglichkeit zum Standardfall wird.

Funktionen
Im Sinne der einfachen Übersetzbarkeit ist es auch, dass Funktionen ein eigenes Schlüsselwort erhalten, welches sowohl zur Definition als auch als Typkonstruktor dient.

Code: Alles auswählen

// Volle Typisierung. Parametervariablen sind standardmäßig konstant.
fun double(n : int) -> int
{
   return 2 * n;
}
// Automatische Bestimmung des Rückgabetyps ist in der Regel trivial, also Rückgabetyp optional.
fun double(n : int)
{
   return 2 * n;
}
// Wenn uns der Typ nicht interessiert, lassen wir ihn weg, und erhalten ein implizites Template.
fun double(n)
{
   return 2 * n;
}

Code: Alles auswählen

// Kurze Funktionen lassen sich schöner schreiben.
fun double(n) = 2 * n
// Natürlich auch gültig:
fun double(n : int) = 2 * n
fun double(n : int) -> int = 2 * n
Die vielen Varianten sehen zunächst etwas einschüchternd aus, können aber zum Großteil schon bei der Konstruktion des AST dank der expliziten Syntax sehr einfach behandelt werden. Die stark verbesserte Lesbarkeit ist es in meinen Augen auf jeden Fall wert.

Zur Definition von Funktionstypen nutzen wir ganz intuitiv fun als Typkonstruktor:

Code: Alles auswählen

fun updateProgressBar(text : string, progress : double) { ... }
def callback = updateProgressBar
// callback hat Typ fun (string, double) bzw. fun (string, double) -> void
Soweit haben wir den funktionalen Teil, Zeit für etwas Objektorientierung.

Klassen und Objekte
Da unsere Klassen im Kern nur Daten enthalten, haben wir uns für das C-Keyword struct entschieden:

Code: Alles auswählen

struct name
{
   first : string // Implizit: def, auch var möglich.
   last : string
}
Um konsistent zu bleiben, haben wir Konstruktoren durch Placement-Funktionen ersetzt. Das + macht this zum (zunächst nicht konstruierten!) Ergebnisobjekt und erzwingt die Einhaltung zusätzlicher Bedingungen des Konstruktionsablaufs.

Code: Alles auswählen

fun +name(first : string, last : string) -> name
{
   // Konstruktion, keine Zuweisung, deshalb Reihenfolge erzwungen.
   // Lokale Variablen an jeder Stelle möglich, Manipulation von Attributen nach erster Konstruktion derselben.
   this.first = first;
   this.last = last;
}
Auch Move-Semantics haben wir angedacht. Der folgende Konstruktor ersetzt den vorangegangen und deckt analog zu C++ Konstruktion durch Kopie und Verschiebung ab, indem er die Kopie, sofern erforderlich, implizit in den Aufruferkontext verlagert:

Code: Alles auswählen

// '+' vor Parameter erzwingt Kopie statt konstanter Referenz (by Value, in C++ also string statt const string &)
fun +name(+first : string, +last : string) -> name
{
   // ~ analog zu std::move() in Anlehnung an die Destruktorsyntax, weil first und last danach höchst wahrscheinlich ungültig sein werden.
   this.first = ~first;
   this.last = ~last;
}
Übrigens erlaubt diese Syntax beliebige Konstruktornamen, womit auch lästige statische Delegationsmethoden zur Benennung von Konstruktoren entfallen. Ein großer Teil der Konstruktoren (Kopie, Movement, Initialisierungsliste?) sollte auf jeden Fall automatisch generiert werden, die Details erscheinen mir hier aber gerade weniger interessant.

Der Vollständigkeit halber noch die Destruktion, die aber logischerweise genau wie in C++ fast immer ohne expliziten Destruktor korrekt laufen sollte:

Code: Alles auswählen

fun ~name()
{
   // Tue nichts, Destruktion der Attribute läuft automatisch.
   // Dieser Destruktor würde natürlich in einem echten Programm gar nicht erst explizit definiert.
}
Die Manipulation von Objekten durch Funktionen (aka Methoden) sähe wie folgt aus:

Code: Alles auswählen

// & macht v zu manipulierbarer Referenz.
// Instantiierung von vector Template durch Instantiierungsoperator: Template => Template-Argumente
// Implizites Template, Syntax Java-inspiriert: ? leitet inplace die Definition eines Typparameters ein, der im Anschluss verwendet werden darf.
fun append(&v : vector => ?E, e : E)
{
   // Verschiebe e ans Ende von v
}

// Benutzung:
var v : vector => int
append(v, 2)
Der Template-Instantiierungsoperator entstand aus der Not heraus, weil wir keinen vernünftigen Weg sahen, spitze Klammern von den entsprechenden Operatoren zu trennen. Ich bin mir noch nicht sicher, ob er in dieser Form brauchbar wäre. Eventuell wäre es der Sprache auch zuträglich, wenn einige Operatorsymbole durch Text ersetzt würden:

Code: Alles auswählen

fun append(ref v : vector => typename E, copy e : E)
{
   // Verschiebe e ans Ende von v
}
So, mir geht schon die Puste aus, ich mache ein anderes Mal weiter. Wir hatten damals bei weitem noch nicht alles durchdacht, und doch kriege ich nur die grobe Skizze eines Bruchteils dessen in diesem Post unter. Auch das zeigt, wie verflucht viel Arbeit so eine Sprache schlussendlich ist, noch bevor sie überhaupt implementiert wird. ;)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [Sprachentwurf] fun pl

Beitrag von BeRsErKeR »

Wirklich ein sehr interessantes Konzept.
CodingCat hat geschrieben:
  • Die Vereinheitlichung von Methoden und freien Funktionen: Die Verankerung von Methoden in Klassen erschien uns überflüssig; sie führt offensichtlich zu Inkonsistenz beim Aufruf, wenn fremde Module zusätzliche Operationen auf Objekten implementieren. Diese könnten zwar umständlich nachträglich in die Klasse injiziert werden, dies entspricht jedoch in keinster Weise der Kapselung als Grundidee hinter Klassen. Tatsächlich erscheint die traditionelle Umsetzung von Klassen aus meiner heutigen Sicht kein gutes Mittel der Kapselung, weil sie vorschreibt, welche Operationen aus Implementierungsgründen objekteigen sind, und welche sich alleine auf der Objektschnittstelle implementieren lassen.
An sowas hatte ich auch schon gedacht, aber mir fiel bislang noch kein gutes Konzept dafür ein.

CodingCat hat geschrieben:
  • Verbesserte Templates: Templates in C++ sind umständlich zu schreiben und erlauben ohne vollständige Instantiierung nur eine sehr eingeschränkte statische Prüfung. Concepts retten uns hoffentlich mit dem nächsten C++-Standard aus dieser misslichen Lage.
Auch hier kann ich dir nur beipflichten. Ich sehe diesen Punkt aber als ziemlich schwierig an. Die C++-Leute sind ja auch nicht dumm, und daher kann ich mir gut vorstellen, dass ein verbessertes Template-System sehr kompliziert werden könnte.

CodingCat hat geschrieben:
  • Trennung von Typ- und Werte-Kontexten: Um im Rahmen eines bequemen Modulsystems ohne jegliches Vorwissen durch Deklarationen vollständig übersetzen zu können, ist an jeder Stelle klar, ob ein Typ oder ein Wert gemeint ist. Damit erübrigen sich auch Mehrdeutigkeiten bei gleichnamigem Objekt und Objekttyp.
Damit umgeht man natürlich die Notwendigkeit die Syntax von Bezeichnern eindeutig unterscheidbar zu gestalten um Mehrdeutigkeiten zu vermeiden. Ein guter Gedanke.

CodingCat hat geschrieben:Variablen und Typen

Code: Alles auswählen

def a = 2 // Konstante vom Typ int
def b : int = 2 // Konstante vom Typ int mit expliziter Typangabe
var c : string // String-Variable
var d : float = uninitialized // Uninitialisierte float-Variable
d = b -> float // b zu float gecastet
Hier wird bereits deutlich, wie Wert- und Typ-Kontext strikt getrennt sind. Die Trennung von def und var hat den Hintergedanken, dass die Definition von Konstanten mit def in keiner Weise umständlicher als die Definition von Variablen gemacht wird, und so nach Möglichkeit zum Standardfall wird.
Gefällt mir auf den ersten Blick auch recht gut. Mich würde interessieren, wie sowas für Klassenobjekte, Arrays usw aussieht. Also auch die Initialisierung.

CodingCat hat geschrieben:Funktionen
Im Sinne der einfachen Übersetzbarkeit ist es auch, dass Funktionen ein eigenes Schlüsselwort erhalten, welches sowohl zur Definition als auch als Typkonstruktor dient.

Code: Alles auswählen

// Volle Typisierung. Parametervariablen sind standardmäßig konstant.
fun double(n : int) -> int
{
   return 2 * n;
}
// Automatische Bestimmung des Rückgabetyps ist in der Regel trivial, also Rückgabetyp optional.
fun double(n : int)
{
   return 2 * n;
}
// Wenn uns der Typ nicht interessiert, lassen wir ihn weg, und erhalten ein implizites Template.
fun double(n)
{
   return 2 * n;
}

Code: Alles auswählen

// Kurze Funktionen lassen sich schöner schreiben.
fun double(n) = 2 * n
// Natürlich auch gültig:
fun double(n : int) = 2 * n
fun double(n : int) -> int = 2 * n
Die vielen Varianten sehen zunächst etwas einschüchternd aus, können aber zum Großteil schon bei der Konstruktion des AST dank der expliziten Syntax sehr einfach behandelt werden. Die stark verbesserte Lesbarkeit ist es in meinen Augen auf jeden Fall wert.

Zur Definition von Funktionstypen nutzen wir ganz intuitiv fun als Typkonstruktor:

Code: Alles auswählen

fun updateProgressBar(text : string, progress : double) { ... }
def callback = updateProgressBar
// callback hat Typ fun (string, double) bzw. fun (string, double) -> void
Soweit haben wir den funktionalen Teil, Zeit für etwas Objektorientierung.
Ich bin auch für ein Schlüsselwort für Funktionen. Auch die Art, wie du den Rückgabetyp definierst find ich gut. So hatte ich mir das auch gedacht. Die Kurzform ist für Inline-Funktionen eigentlich auch nicht schlecht. Durch eure Typ/Wert-Trennung sind Funktionszeiger, wie du zeigst, auch recht einfach zu realisieren. Gefällt mir.

CodingCat hat geschrieben: Auch Move-Semantics haben wir angedacht. Der folgende Konstruktor ersetzt den vorangegangen und deckt analog zu C++ Konstruktion durch Kopie und Verschiebung ab, indem er die Kopie, sofern erforderlich, implizit in den Aufruferkontext verlagert:

Code: Alles auswählen

// '+' vor Parameter erzwingt Kopie statt konstanter Referenz (by Value, in C++ also string statt const string &)
fun +name(+first : string, +last : string) -> name
{
   // ~ analog zu std::move() in Anlehnung an die Destruktorsyntax, weil first und last danach höchst wahrscheinlich ungültig sein werden.
   this.first = ~first;
   this.last = ~last;
}
Ich nehme an, dass der Operator für binäres Invertieren (~) dann anders aussieht?! Oder ist das abhängig davon, ob der Code im Kostruktor liegt? Dann könnte man aber diesen Operator nicht im Konstruktor nutzen. Vom Typ abhängig kanns ja auch nicht sein, weil man ja auch ints als Parameter nutzen kann.
Ohne Input kein Output.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von dot »

An eine Vereinheitlichung von Methoden und Funktionen hab ich auch schon öfter mal gedacht. Was ich mich dabei nur grad frag: Wie genau wollt ihr sowas wie virtuelle Methoden realisieren? Evtl. ein allgemeines Multidispatch System?
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von CodingCat »

BeRsErKeR hat geschrieben:
CodingCat hat geschrieben:
  • Verbesserte Templates: Templates in C++ sind umständlich zu schreiben und erlauben ohne vollständige Instantiierung nur eine sehr eingeschränkte statische Prüfung. Concepts retten uns hoffentlich mit dem nächsten C++-Standard aus dieser misslichen Lage.
Auch hier kann ich dir nur beipflichten. Ich sehe diesen Punkt aber als ziemlich schwierig an. Die C++-Leute sind ja auch nicht dumm, und daher kann ich mir gut vorstellen, dass ein verbessertes Template-System sehr kompliziert werden könnte.
Ja, aus diesem Grund haben wir uns auch von der Funktionsweise ziemlich an C++ Templates gehalten. Die Redundanz lässt sich vergleichsweise einfach durch das Weglassen von Deklarationen reduzieren. Aber die genaue Spezifikation und Implementierung der Funktionsweise, und sei sie "nur" C++ mit Concepts, wäre mit Sicherheit nicht-trivial.
BeRsErKeR hat geschrieben:
CodingCat hat geschrieben:Variablen und Typen

Code: Alles auswählen

def a = 2 // Konstante vom Typ int
def b : int = 2 // Konstante vom Typ int mit expliziter Typangabe
var c : string // String-Variable
var d : float = uninitialized // Uninitialisierte float-Variable
d = b -> float // b zu float gecastet
Hier wird bereits deutlich, wie Wert- und Typ-Kontext strikt getrennt sind. Die Trennung von def und var hat den Hintergedanken, dass die Definition von Konstanten mit def in keiner Weise umständlicher als die Definition von Variablen gemacht wird, und so nach Möglichkeit zum Standardfall wird.
Gefällt mir auf den ersten Blick auch recht gut. Mich würde interessieren, wie sowas für Klassenobjekte, Arrays usw aussieht. Also auch die Initialisierung.
Die Initialisierung läuft für Objekte in keiner Weise anders. Ich fand C++' Konstruktornotation immer etwas unnatürlich, zumal auch dort am Ende Zuweisungen zum Glück eh wieder zu reinen Konstruktionen werden. Der Plan wäre also, alle Objektkonstruktionen ganz intuitiv als Zuweisungen zu schreiben. Dies ist insbesondere konsistent mit der uneingeschränkten Namensgebung bei Placement-Funktionen als Konstruktoren. Schlussendlich läuft das also einfach auf garantierte Copy Elision bei Zuweisung in einer Initialisierung raus:

Code: Alles auswählen

def a = (std.vector => int)() // Anmerkung: Mit der Template-Spezialisierungsnotation bin ich bis heute nicht zufrieden. ;-)
def b = math.vector(2.0f, 3.0f, 4.0f)
...
Arrays hatten wir ursprünglich klassisch als eingebaute Typkonstruktoren geplant (int[4]), allerdings bin ich mir heute nicht mehr sicher, ob hier nicht ein entsprechendes Template in der Standardbibliothek angebrachter wäre. Dazu müsste jedoch wohl dringend die Template-Notation verbessert werden (array=>(int, 5) :mrgreen:). Im Optimalfall würde man der Sprache dann noch Initialisierungslisten beibringen, wie C++11 sie ebenfalls kennt.

Auch hier wird deutlich, wie frustrierend die Situation mit C++ eigentlich ist: Die Mechanismen der Sprache sind phänomenal und vor allem zum Großteil heute schon einsatzbereit. Wäre die Grammatik nicht so kompliziert und das Modulsystem so grundlegend verfehlt, ließe sich der Rest mit relativ wenig Mühe zu einer großartigen Sprache entwickeln. Allerdings ginge auch dies wohl kaum ohne einen Bruch mit der Abwärtskompatibilität, es sei denn, C++ würde so etwas wie Feature Levels einführen, worüber ich aber gar nicht nachdenken mag.
BeRsErKeR hat geschrieben:Ich nehme an, dass der Operator für binäres Invertieren (~) dann anders aussieht?! Oder ist das abhängig davon, ob der Code im Kostruktor liegt? Dann könnte man aber diesen Operator nicht im Konstruktor nutzen. Vom Typ abhängig kanns ja auch nicht sein, weil man ja auch ints als Parameter nutzen kann.
Ja, der Operator zur Bitnegation müsste dann selbstverständlich anders aussehen. Mit etwas Distanz betrachtet war der Verschiebe-Operator aber wohl keine gute Idee. Ich denke es wäre hier zu Gunsten der Konvention angebrachter, den Verschiebe-Operator fallen zu lassen, und analog zu C++ durch eine move-Funktion zu ersetzen. ~ als Typkonstruktor könnte natürlich bestehen bleiben.
dot hat geschrieben:An eine Vereinheitlichung von Methoden und Funktionen hab ich auch schon öfter mal gedacht. Was ich mich dabei nur grad frag: Wie genau wollt ihr sowas wie virtuelle Methoden realisieren? Evtl. ein allgemeines Multidispatch System?
Auf Sprachebene haben wir geplant, dynamische Schnittstellen mit statischen Concepts zu vereinen. Hierzu haben wir das class-Keyword wieder eingeführt, welches hier jedoch mehr im Sinne eine Haskell-Klasse oder eines C++-Concepts zu verstehen ist:

Code: Alles auswählen

// Plugin hier zunächst als statische Schnittstelle, wie C++ Concepts.
class Plugin
{
	fun name(plugin : Plugin) -> string
	// Kein schönes Beispiel bzgl. Architektur, mir fällt gerade nichts besseres ein.
	fun load(plugin : Plugin, editor : Editor)
}

// Exemplarisch teilweise mit Standardimplementierung.
fun name(plugin : Plugin) = "unnamed plugin"

Code: Alles auswählen

struct ConsolePlugin
{
	widget : ConsoleWidget
}
// Prüfe ConsolePlugin statisch auf vollständige Implementierung von Plugin.
instance ConsolePlugin : Plugin

fun name(plugin : ConsolePlugin) -> string = "console plugin"
fun load(plugin : ConsolePlugin, editor : Editor)
{
	// ...
}
Soweit statische Prüfung mit Klassen (Concepts). Die Idee war nun, diese Klassen (Concepts) einfach auch als dynamische Interfaces zuzulassen:

Code: Alles auswählen

instance ConsolePlugin : virtual Plugin
Die Frage ist, in wieweit sich das effizient durch V-Tables o.ä. umsetzen lässt. Für jede virtuelle Klasseninstantiierung einen V-Pointer einzufügen, sollte nicht das Problem sein. Bei vielen Interfaces hätte man jedoch dasselbe Problem wie in der entsprechenden aktuellen MSVC++-Implementierung, haufenweise V-Pointers für potentiell wenig Funktionalität.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von CodingCat »

Btw. habe ich noch gar nichts zu Zugriffsbeschränkung gesagt. Hier vertrete ich persönlich sogar die Meinung von Python, dass (erzwungene) Zugriffsbeschränkung schlussendlich wenig Gewinn bringt, wenn man vernünftig programmiert. Achtung, ich rede hier ausdrücklich nur von der Erzwingung durch den Compiler. Es ist klar, dass die Sprache auf jeden Fall Mittel zum Ausdruck der intendierten Benutzung bereitstellen sollte.

So sollte für fremde Benutzer auf jeden Fall immer klar sein, welche Funktionen vollständig (Erfüllung aller Invarianten vor und nach Ausführung) und somit für den allgemeinen Gebrauch bestimmt sind, und welche Funktionen nur Hilfsfunktionen und somit ausdrücklich nicht für den allgemeinen Gebrauch bestimmt sind. Eine Klassifikation von (modul-)externen und internen Funktionen wäre also auf jeden Fall notwendig. Hier schwebt mir ein internal-Namensraum vor, das heißt der Aufruf interner Funktionen ginge nur über internal.privateFunction(), wäre aber prinzipiell von überall möglich.

Schwieriger ist das bei Daten. C++' Trennung von privaten und öffentlichen Daten sagt mir überhaupt nicht zu. Tatsächlich ist doch die einzige Möglichkeit, das Geheimnisprinzip umzusetzen, stets alle Daten private zu markieren, sofern man nicht über abstruse Proxy-Objekt-Konstrukte mit Operatorüberladung und Property-Semantik geht. Damit ist die Zugriffsbeschränkung für mich eigentlich vollkommen nutzlos. Manche Objekte sind offensichtlich Datenobjekte (z.B. mathematische Objekte), dann sind per Konvention alle Daten öffentlich. Ansonsten sind per Konvention alle Daten privat, ein direkter Zugriff sollte also grundsätzlich nicht stattfinden. Im Rahmen der Vereinheitlichung von Funktionen und Methoden wäre es Unfug, den Zugriff generell zu verbieten, irgendwo muss die private Funktionalität schließlich hin.

Das Problem besteht übrigens auch in C++, mit grauenvollen Konsequenzen: Um Zugriffskontrolle überhaupt umsetzen zu können, erfordert C++, wie die meisten anderen objektorientierten Sprachen, die Verankerung von privaten Methoden in der Klasse. In C++ bedeutet dies jedoch, dass private Methoden in der öffentlichen Schnittstelle der Klasse deklariert werden müssen. Schlimmer noch, sie wirken sich sogar auf die Namensauflösung außerhalb der Klasse aus. Zusammen mit dem textbasierten "Modulsystem" (auch Änderung privater Daten/Methoden erfordert allgemeines Neucompilieren!) ist die Zugriffsbeschränkung in C++ einer der größten Produktivitätskiller überhaupt, den ich inzwischen mit einigen unschönen Tricks zum Glück einigermaßen im Griff habe.

Sofern die Sprache dadurch nicht zu kompliziert wird, wäre auch hier eine Alternative, Mittel zum Ausdruck der Intention bereitzustellen. Dann müssten Funktionen, die von Implementierungsdetails abhängen, ihre Abhängigkeit entsprechend kenntlich machen:

Code: Alles auswählen

fun someMethod(internal someObject : SomeClass)
{
   // Zugriff auf Interna von someObject erlaubt
}
Natürlich müsste man dann auch zwischen privaten und öffentlichen Daten unterscheiden. Eventuell bietet sich hier eine Rückkehr zu den Konventionen anderer Sprachen an, indem man die bisher vorgestellten Schlüsselwörter neu zuordnet. struct könnte dann für öffentliche Daten stehen, class für private, und aus dem bisherigen, gerade erst im Post obendrüber eingeführten class-Schlüsselwort würde interface.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von CodingCat »

Eine sehr interessante wie einfache Methode für die effiziente Umsetzung von Interfaces, die wohl auch in einer etwas laufzeitorientierteren Form in Go zum Einsatz kommt, sind Interface-Zeiger. Anstatt den V-Pointer mit in das Objekt zu packen, haben Zeiger auf Interfaces intern zwei Werte (also 64-128 Bit?), einen Zeiger auf das Objekt, und einen auf den V-Table des jeweiligen Interfaces. Solche Zeiger entstehen implizit beim Upcast. Dadurch lässt sich dynamische Bindung zugleich transparent und dezentral realisieren. Eine Klasse kann ohne Overhead beliebig viele Interfaces implementieren. Nicht mal mehr die manuelle virtuelle Instantiierung eines Interfaces durch eine Klasse ist damit notwendig, jede Klasse, die ein beliebiges Interface erfüllt, implementiert dies auch automatisch (V-Table-Generierung durch den Compiler beim ersten Upcast). Manuelle Instantiierung wird dann nur noch für Bibliotheksfunktionalität ähnlich derer von DLLs benötigt, aber das ist ein ganz eigenes (hässliches?) Kapitel.

Nachtrag: Mir fällt allerdings im Rahmen dieser Methode keine dezentrale Umsetzung von (unsicheren) Downcasts in Interface-Hierarchien ein. Die Frage ist, ob dieser Fall überhaupt relevant ist, oder einfach verboten werden kann.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
klickverbot
Establishment
Beiträge: 191
Registriert: 01.03.2009, 19:22
Echter Name: David N.

Re: [Sprachentwurf] fun pl

Beitrag von klickverbot »

BeRsErKeR hat geschrieben:
CodingCat hat geschrieben:
  • Verbesserte Templates: Templates in C++ sind umständlich zu schreiben und erlauben ohne vollständige Instantiierung nur eine sehr eingeschränkte statische Prüfung. Concepts retten uns hoffentlich mit dem nächsten C++-Standard aus dieser misslichen Lage.
Auch hier kann ich dir nur beipflichten. Ich sehe diesen Punkt aber als ziemlich schwierig an. Die C++-Leute sind ja auch nicht dumm, und daher kann ich mir gut vorstellen, dass ein verbessertes Template-System sehr kompliziert werden könnte.
Einer der Hauptgründe, warum C++-Templates so umständlich zu verwenden sind, ist, dass sie ursprünglich für weitaus bescheidenere Ziele als Modern C++ und boost::mpl entwickelt wurden – viele der Anwendungen wurden erst im Laufe der Zeit »gefunden«. D, zum Beispiel, hat ein ähnliches (sogar noch umfangreicheres, aber dadurch einheitlicheres) Template-Design, das aber aus meiner Sicht weitaus angenehmer zu benutzen ist, da eben beim Design schon auf die Erfahrungen aus C++ zurückgegriffen werden konnte.

Was einem aber in jedem Fall einiges an Gehirnschmalz abverlangen wird, sind Design und Implementierung der Regeln für Template Matching (bei Vorliegen evt. mehrerer Spezialisierungen) und Template Argument Deduction (für Function Templates). Alle Details sauber hinzubekommen, vor allem im Bezug auf die Interaktion mit anderen (möglichen) Sprachfeatures wie const, ist alles andere als leicht.
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von Helmut »

CodingCat hat geschrieben:Eine sehr interessante wie einfache Methode für die effiziente Umsetzung von Interfaces, die wohl auch in einer etwas laufzeitorientierteren Form in Go zum Einsatz kommt, sind Interface-Zeiger. Anstatt den V-Pointer mit in das Objekt zu packen, haben Zeiger auf Interfaces intern zwei Werte (also 64-128 Bit?), einen Zeiger auf das Objekt, und einen auf den V-Table des jeweiligen Interfaces. Solche Zeiger entstehen implizit beim Upcast. Dadurch lässt sich dynamische Bindung zugleich transparent und dezentral realisieren. Eine Klasse kann ohne Overhead beliebig viele Interfaces implementieren. Nicht mal mehr die manuelle virtuelle Instantiierung eines Interfaces durch eine Klasse ist damit notwendig, jede Klasse, die ein beliebiges Interface erfüllt, implementiert dies auch automatisch (V-Table-Generierung durch den Compiler beim ersten Upcast). Manuelle Instantiierung wird dann nur noch für Bibliotheksfunktionalität ähnlich derer von DLLs benötigt, aber das ist ein ganz eigenes (hässliches?) Kapitel.

Nachtrag: Mir fällt allerdings im Rahmen dieser Methode keine dezentrale Umsetzung von (unsicheren) Downcasts in Interface-Hierarchien ein. Die Frage ist, ob dieser Fall überhaupt relevant ist, oder einfach verboten werden kann.
Eine wirklich interessante Sache. Allerdings würde das Feature, so wie ich das verstehe, einen GC zwingend voraussetzen, da die VTable keinen wirklichen Besitzer hat. Das Objekt selbst kann nicht der Besitzer sein, da es pro Instanz mehrere VTables geben kann und der Zeiger selbst auch nicht, da sonst eine einfache Zeigerübergabe eine Heapallokation bedeuten würde.



Mich würde mal interessieren wie ihr Zeiger implementieren würdet. Ein beliebter Kritikpunkt an C++ ist ja, dass man immer zwischen . :: und -> unterscheiden muss. Die ersten beiden zu kombinieren ist kein Problem, -> loszuwerden macht zB D, indem es alles zu Zeigern macht und die Platzierung von Objekten auf dem Stack dem Optimierer überlässt. Eine eher suboptimale Lösung wie ich finde.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von CodingCat »

Helmut hat geschrieben:Eine wirklich interessante Sache. Allerdings würde das Feature, so wie ich das verstehe, einen GC zwingend voraussetzen, da die VTable keinen wirklichen Besitzer hat. Das Objekt selbst kann nicht der Besitzer sein, da es pro Instanz mehrere VTables geben kann und der Zeiger selbst auch nicht, da sonst eine einfache Zeigerübergabe eine Heapallokation bedeuten würde.
V-Tables sind doch statisch, die brauchen prinzipiell weder Besitzer noch GC. :)
Helmut hat geschrieben:Mich würde mal interessieren wie ihr Zeiger implementieren würdet. Ein beliebter Kritikpunkt an C++ ist ja, dass man immer zwischen . :: und -> unterscheiden muss. Die ersten beiden zu kombinieren ist kein Problem, -> loszuwerden macht zB D, indem es alles zu Zeigern macht und die Platzierung von Objekten auf dem Stack dem Optimierer überlässt. Eine eher suboptimale Lösung wie ich finde.
Ja, D nutzt ja auch einen GC, was ich schon alleine für eine sehr fragwürdige Design-Entscheidung halte. Das eigentliche Problem sind aber nicht rohe Zeiger, sondern Smart Pointers und dergleichen, die auf jeden Fall gut umsetzbar sein sollten. Der C++-Weg ist mit der Unterscheidung von . und -> zwar nicht unbedingt optimal in der Benutzung, aber in dieser Hinsicht sehr konsistent.

Eine möglicherweise bessere Alternative wäre, den Operator . überladbar zu machen, und zusätzlich einen zweiten, nicht überladbaren Operator zur Verfügung zu stellen, der stets dem ursprünglichen Operator . ohne Überladung entspricht. Da auch bei Smart Pointers der Aufruf von Smart Pointer-eigenen Methoden eher selten ist, wird so der Standardfall wesentlich einfacher. Auf Zeigern entspricht damit Operator . per Definition einer Dereferenzierung (kein Problem, da vorher überhaupt keine Bedeutung). Auf Smart Pointers wird der überladene Operator . aufgerufen. Zur Smart Pointer-Manipulation kann der nicht überladbare Alternativoperator genutzt werden. Hierfür -> als Operatorsymbol zu nutzen, würde vermutlich eine ganze Programmierergeneration zur Verzweiflung bringen. :: wäre denkbar:

Code: Alles auswählen

scoped_ptr<Foo> a(&foo);
a.bar();
Foo *b = a::detach()
Im Übrigen hat das für das hier angedachte Sprachkonzept kaum Bewandnis, solange nicht Aufrufe mit Bezugsobjekt als Alternative zu Funktionsaufrufen zugelassen werden (was jedoch durchaus sinnvoll sein könnte). Auf jeden Fall ein interessantes Gedankenspiel.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von Helmut »

CodingCat hat geschrieben:
Helmut hat geschrieben:Eine wirklich interessante Sache. Allerdings würde das Feature, so wie ich das verstehe, einen GC zwingend voraussetzen, da die VTable keinen wirklichen Besitzer hat. Das Objekt selbst kann nicht der Besitzer sein, da es pro Instanz mehrere VTables geben kann und der Zeiger selbst auch nicht, da sonst eine einfache Zeigerübergabe eine Heapallokation bedeuten würde.
V-Tables sind doch statisch, die brauchen prinzipiell weder Besitzer noch GC. :)
In C++ schon. In Go müssten sie aber dynamisch allokiert werden. Und zwar bei jedem Upcast, da die Basisklasse die virtuellen Methoden ja zB in einer anderen Reihenfolge deklarieren kann.
Ich würde also bei den klassischen VTables bleiben und auch Mehrfachvererbung beibehalten. Dann kann man schonmal Zeiger haben, die doppelt so groß sind wie sie eigentlich sollten, aber ansonsten sind sie eigentlich unproblematisch.
Helmut hat geschrieben:Mich würde mal interessieren wie ihr Zeiger implementieren würdet. Ein beliebter Kritikpunkt an C++ ist ja, dass man immer zwischen . :: und -> unterscheiden muss. Die ersten beiden zu kombinieren ist kein Problem, -> loszuwerden macht zB D, indem es alles zu Zeigern macht und die Platzierung von Objekten auf dem Stack dem Optimierer überlässt. Eine eher suboptimale Lösung wie ich finde.
Ja, D nutzt ja auch einen GC, was ich schon alleine für eine sehr fragwürdige Design-Entscheidung halte. Das eigentliche Problem sind aber nicht rohe Zeiger, sondern Smart Pointers und dergleichen, die auf jeden Fall gut umsetzbar sein sollten. Der C++-Weg ist mit der Unterscheidung von . und -> zwar nicht unbedingt optimal in der Benutzung, aber in dieser Hinsicht sehr konsistent.

Eine möglicherweise bessere Alternative wäre, den Operator . überladbar zu machen, und zusätzlich einen zweiten, nicht überladbaren Operator zur Verfügung zu stellen, der stets dem ursprünglichen Operator . ohne Überladung entspricht. Da auch bei Smart Pointers der Aufruf von Smart Pointer-eigenen Methoden eher selten ist, wird so der Standardfall wesentlich einfacher. Auf Zeigern entspricht damit Operator . per Definition einer Dereferenzierung (kein Problem, da vorher überhaupt keine Bedeutung). Auf Smart Pointers wird der überladene Operator . aufgerufen. Zur Smart Pointer-Manipulation kann der nicht überladbare Alternativoperator genutzt werden. Hierfür -> als Operatorsymbol zu nutzen, würde vermutlich eine ganze Programmierergeneration zur Verzweiflung bringen. :: wäre denkbar:

Code: Alles auswählen

scoped_ptr<Foo> a(&foo);
a.bar();
Foo *b = a::detach()
Im Übrigen hat das für das hier angedachte Sprachkonzept kaum Bewandnis, solange nicht Aufrufe mit Bezugsobjekt als Alternative zu Funktionsaufrufen zugelassen werden (was jedoch durchaus sinnvoll sein könnte). Auf jeden Fall ein interessantes Gedankenspiel.
Ja bei Smartpointern würde ich auch so einen operator vorschlagen. Macht D auch so. Wobei D die Smartpointer eigenen Methoden auch über den Punkt weiterhin erreichbar macht. Falls es einen Namenskonflikt gibt wirft D einfach nen Fehler.

Um die klassischen Zeiger in den Griff zu kriegen könnte man sie einfach wie Referenzen behandeln, wobei der Adressoperator einen LValue auf den eigentlichen Zeiger liefert:

Code: Alles auswählen

int a = 0, b = 0;
int* c = &a;
c = 1;
&c = b;
c = 2;
&c = null;
assert(a == 1 && b == 2);
Ein weiterer Vorteil von der Syntax wäre, dass es praktisch keinen Unterschied zwischen Referenzen und Zeigern gibt. Man könnte festlegen, dass lediglich Zeiger null sein dürfen.
antisteo
Establishment
Beiträge: 854
Registriert: 15.10.2010, 09:26
Wohnort: Dresdem

Re: [Sprachentwurf] fun pl

Beitrag von antisteo »

CodingCat hat geschrieben:Btw. habe ich noch gar nichts zu Zugriffsbeschränkung gesagt. Hier vertrete ich persönlich sogar die Meinung von Python, dass (erzwungene) Zugriffsbeschränkung schlussendlich wenig Gewinn bringt, wenn man vernünftig programmiert. Achtung, ich rede hier ausdrücklich nur von der Erzwingung durch den Compiler. Es ist klar, dass die Sprache auf jeden Fall Mittel zum Ausdruck der intendierten Benutzung bereitstellen sollte.
Bei größeren Teams und komplizierteren Sachverhalten ist es schon wichtig, gewisse Constraints durchzusetzen. So eine Art Assertions wäre nett: Dass eine gewisse Funktion nie im Kontext einer bestimmten Klasse oder einer Gruppe von Klassen aufgerufen wird. Damit könnte man zum Beispiel die MVC-Einteilung vom Compiler überprüfen lassen. Ich verbringt viel Zeit damit, Crosscutting Concerns aufzulösen. Da wäre ein programmierbarer, compilerinterner Zugriffsbeschränker eine große Hilfe.
http://fedoraproject.org/ <-- freies Betriebssystem
http://launix.de <-- kompetente Firma
In allen Posts ist das imo und das afaik inbegriffen.
klickverbot
Establishment
Beiträge: 191
Registriert: 01.03.2009, 19:22
Echter Name: David N.

Re: [Sprachentwurf] fun pl

Beitrag von klickverbot »

Helmut hat geschrieben:[…] Die ersten beiden zu kombinieren ist kein Problem, -> loszuwerden macht zB D, indem es alles zu Zeigern macht und die Platzierung von Objekten auf dem Stack dem Optimierer überlässt. Eine eher suboptimale Lösung wie ich finde.
Das stimmt so nur eingeschränkt. Ich möchte den Thread jetzt nicht in eine Diskussion über D abdriften lassen, aber die Sprache kennt grundsätzlich zwei Konzepte, die man als »Objekt« bezeichnen könnte.

Einerseits Klassen (class). Sie sind, und das ist der wirkliche Unterschied zu C++, immer Typen mit Referenzcharakter (um Dinge wie das Slicing-Problem zu vermeiden). Ja, sie werden durch new standardmäßig am GC-Heap erzeugt, aber genauso lassen sich Instanzen in einem per C `malloc` oder sogar `alloca` Stück Speicher ablegen (emplace, scoped).

Strukturen (struct), hingegen, haben selbst immer value type semantics, wie von C++ gewohnt. Sie können beispielsweise POD-Typen sein, aber genauso auch Member-Methoden, etc. haben. Generell sind sie flexibler, und können semantisch gesehen tatsächlich Typen mit Referenzcharakter repräsentieren (ref-counted file handles würde man z.B. als struct implementieren). Bloß die eingebauten, »Java-style« Mechanismen zu Vererbung und Polymorphie sind mit ihnen nicht benutzbar (mit etwas Template-Magie ist es aber möglich, vtables usw. durchaus komfortabel benutzbar zu implementieren – ja, das wurde tatsächlich schon gemacht…).

Klassen sind also speziell für die in manchen Stilen allgegenwärtigen »Java«-Objekte gedacht, während man sich mit Strukturen die gewünschten Eigenschaften selbst zusammenbasteln darf…
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von CodingCat »

Helmut hat geschrieben:
CodingCat hat geschrieben:V-Tables sind doch statisch, die brauchen prinzipiell weder Besitzer noch GC. :)
In C++ schon. In Go müssten sie aber dynamisch allokiert werden. Und zwar bei jedem Upcast, da die Basisklasse die virtuellen Methoden ja zB in einer anderen Reihenfolge deklarieren kann.
Ich sehe kein Problem damit, solche Interface-Tabellen, wie Go sie nutzt, schon zur Compile-Zeit anzulegen. Zur Compile-Zeit sind schließlich alle Upcasts bekannt.
Helmut hat geschrieben:Ich würde also bei den klassischen VTables bleiben und auch Mehrfachvererbung beibehalten. Dann kann man schonmal Zeiger haben, die doppelt so groß sind wie sie eigentlich sollten, aber ansonsten sind sie eigentlich unproblematisch.
Bei klassischen V-Tables ändert sich die Zeigergröße doch gar nicht. Dafür wächst die Größe der Objekte selbst linear mit der Anzahl der Interfaces. Und in Bezug auf Erweiterbarkeit bleiben klassische V-Tables ein unschöner Kompromiss, weil sie zumindest intern eine feste Vorauswahl von dynamisch gebundenen objekteigenen Methoden erfordern.
Ja bei Smartpointern würde ich auch so einen operator vorschlagen. Macht D auch so. Wobei D die Smartpointer eigenen Methoden auch über den Punkt weiterhin erreichbar macht. Falls es einen Namenskonflikt gibt wirft D einfach nen Fehler.

Um die klassischen Zeiger in den Griff zu kriegen könnte man sie einfach wie Referenzen behandeln, wobei der Adressoperator einen LValue auf den eigentlichen Zeiger liefert:

Code: Alles auswählen

int a = 0, b = 0;
int* c = &a;
c = 1;
&c = b;
c = 2;
&c = null;
assert(a == 1 && b == 2);
Ein weiterer Vorteil von der Syntax wäre, dass es praktisch keinen Unterschied zwischen Referenzen und Zeigern gibt. Man könnte festlegen, dass lediglich Zeiger null sein dürfen.
So ist das aber ziemlich inkonsistent. Entweder int* c = a; oder &c = &b;. Gefällt mir aber nicht wirklich; ich denke, Zeiger sollten sich in erster Linie auch wie Zeiger verhalten. Mit deinem Vorschlag wären Zeiger nicht mal mehr konsistent mit Smart Pointers. Zudem erscheint mir dein Anwendungsfall c = 1 und c = 2 im normalen Programmieralltag äußerst selten, ausgerechnet dieser wird so jedoch implizit zum Standardfall erklärt. (In Managed-Sprachen z.B. gibt es diesen Fall für Objekte überhaupt nicht.)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von Helmut »

CodingCat hat geschrieben:
Helmut hat geschrieben:
CodingCat hat geschrieben:V-Tables sind doch statisch, die brauchen prinzipiell weder Besitzer noch GC. :)
In C++ schon. In Go müssten sie aber dynamisch allokiert werden. Und zwar bei jedem Upcast, da die Basisklasse die virtuellen Methoden ja zB in einer anderen Reihenfolge deklarieren kann.
Ich sehe kein Problem damit, solche Interface-Tabellen, wie Go sie nutzt, schon zur Compile-Zeit anzulegen. Zur Compile-Zeit sind schließlich alle Upcasts bekannt.
Naja angenommen du schreibst eine simple Funktion, die eine Klasse A als Parameter annimmt und ein Interface vom Typ B zurückgibt, wobei Klasse A alle Methoden von B implementiert. (also einfach B* foo(A* a) { return a;}). Nach der Methode von Go muss die Funktion also intern zwei Zeiger zurückgeben. Der erste Zeiger, die eigentliche Instanz, kann von a übernommen werden. Die VTable muss aber, da in der VTable von a (normalerweise) mehr Einträge sind und die Einträge von B auch nicht notgedrungen am Anfang der VTable von A stehen (weil in Go ja A nicht von B erben muss), zur Laufzeit neu erstellt werden. Erstellt kann die VTable aber weder auf dem Stack, noch im Objekt a (das würde ja die VTables anderer Zeiger zerstören). Statisch geht auch nicht, weil a ja auf eine Unterklasse von A zeigen kann. Bleibt also nur der Heap. Oder ich hab irgendwas falsch verstandan.. :)
Helmut hat geschrieben:Ich würde also bei den klassischen VTables bleiben und auch Mehrfachvererbung beibehalten. Dann kann man schonmal Zeiger haben, die doppelt so groß sind wie sie eigentlich sollten, aber ansonsten sind sie eigentlich unproblematisch.
Bei klassischen V-Tables ändert sich die Zeigergröße doch gar nicht. Dafür wächst die Größe der Objekte selbst linear mit der Anzahl der Interfaces. Und in Bezug auf Erweiterbarkeit bleiben klassische V-Tables ein unschöner Kompromiss, weil sie zumindest intern eine feste Vorauswahl von dynamisch gebundenen objekteigenen Methoden erfordern.
Ich meinte, dass bei Mehrfachvererbung einige Zeiger beispielsweise auf einem 32Bit System auch mal 64Bit groß sein können. (siehe hier)
Ja bei Smartpointern würde ich auch so einen operator vorschlagen. Macht D auch so. Wobei D die Smartpointer eigenen Methoden auch über den Punkt weiterhin erreichbar macht. Falls es einen Namenskonflikt gibt wirft D einfach nen Fehler.

Um die klassischen Zeiger in den Griff zu kriegen könnte man sie einfach wie Referenzen behandeln, wobei der Adressoperator einen LValue auf den eigentlichen Zeiger liefert:

Code: Alles auswählen

int a = 0, b = 0;
int* c = &a;
c = 1;
&c = b;
c = 2;
&c = null;
assert(a == 1 && b == 2);
Ein weiterer Vorteil von der Syntax wäre, dass es praktisch keinen Unterschied zwischen Referenzen und Zeigern gibt. Man könnte festlegen, dass lediglich Zeiger null sein dürfen.
So ist das aber ziemlich inkonsistent. Entweder int* c = a; oder &c = &b;. Gefällt mir aber nicht wirklich; ich denke, Zeiger sollten sich in erster Linie auch wie Zeiger verhalten. Mit deinem Vorschlag wären Zeiger nicht mal mehr konsistent mit Smart Pointers. Zudem erscheint mir dein Anwendungsfall c = 1 und c = 2 im normalen Programmieralltag äußerst selten, ausgerechnet dieser wird so jedoch implizit zum Standardfall erklärt. (In Managed-Sprachen z.B. gibt es diesen Fall für Objekte überhaupt nicht.)
Hm, ja, es sollte wohl int* c = a; sein. Die Syntax hätte den Vorteil, dass man den Operator -> nicht mehr braucht. Sieht man im Beispiel nur dummerweise nicht :)
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Sprachentwurf] fun pl

Beitrag von CodingCat »

Helmut hat geschrieben:Statisch geht auch nicht, weil a ja auf eine Unterklasse von A zeigen kann. Bleibt also nur der Heap. Oder ich hab irgendwas falsch verstandan.. :)
Naja, Vererbung abseits der Implementierung von Interfaces war bis hierher von meiner Seite schlichtweg nicht vorgesehen. ;)
Helmut hat geschrieben:Hm, ja, es sollte wohl int* c = a; sein. Die Syntax hätte den Vorteil, dass man den Operator -> nicht mehr braucht. Sieht man im Beispiel nur dummerweise nicht :)
Operator -> braucht man deshalb nicht, weil . auf Zeigern ohnehin nicht definiert ist. Der Rest muss sich deshalb aber noch lange nicht weg von Zeiger- zu Referenzsemantik ändern.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Antworten