Winkelfunktionen

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • tsunamix schrieb:

    Amin Negm-Awad schrieb:

    macmoonshine schrieb:

    tsunamix schrieb:

    Ich frage mich, so aus reiner Neugierde in dem Zusammenhang, ob und wie man das generisch implementieren könnte, so daß das auf Floats und Doubles gleichermaßen anwendbar wird und man das nicht für beide Typen separat schreiben müßte.
    Dazu müssten der Multiplikations- und Divisionsoperator über ein gemeinsames Protokoll von Double und Float deklariert werden.
    Würde es nicht reichen, ein Protokoll "Degreeable" zu haben, welches ich auf Floats und Doubles anwende? Uiuiuiui, ich sehe eine Casting-Wüste vor mir.
    Man kann ein Protokoll deklarieren, sicher. Das muß man dann aber immernoch für Floats und Doubles getrennt implementieren, also zweimal. Meine Frage war, ob und wenn ja, wie man das mit einer gererischen Implementierung machen kann.
    Nur zur Klarheit. Ich behaupte keinesfalls, daß das hier sinnvoll sei. Ganz im Gegenteil. Mich interessiert hier nur rein theoretisch das Ob und Wie.
    Ich habe ja auch nicht auf dich geantwortet, sondern auf moonie. Und sein Ansatz verhindert ja nicht die doppelte Implementierung. Und dann kann ich gleich ein eigenes Protokoll nehmen.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?
  • macmoonshine schrieb:

    Das ist übrigens ein Fallbeispiel, dass den Unterschied zwischen C++-Templates und (Swift-) Generics sehr gut illustriert. C++-Templates sind im Prinzip ja bloß blöde Makros, die über den Parametertypen nur das wissen müssen, was sie auch brauchen.
    Auch wenn es seit Java üblich geworden ist, mit Generics C++-Templates mit ein bisschen Parmesan (ich spreche hier absichtlich nicht von Parmigiano Reggiano) obendrauf zu bezeichnen, ist das nicht richtig. Generics ("Allgemeingültiges") ist jeder Code, der typunabhängig funktioniert, also auch etwa über dynamische Typisierung. ("Wieso hat Objective-C keine Generics?" – "Hat es, es hat bloß keine Templates.")

    Bei Swift sind das auch Templates: Vorlagencode (Templatecode) für den typabhängiger Code instantiert wird. Durch Umbenennung wird das nicht besser.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?
  • Amin Negm-Awad schrieb:

    Und sein Ansatz verhindert ja nicht die doppelte Implementierung.
    Wenn es funktionieren würde, was es aber nicht tut, müsste man die Methode degree nur einmal implementieren. Der Overhead dafür ist natürlich indiskutabel.

    Amin Negm-Awad schrieb:

    Bei Swift sind das auch Templates: Vorlagencode (Templatecode) für den typabhängiger Code instantiert wird. Durch Umbenennung wird das nicht besser.
    Ich sehe da einen Unterschied in der Umsetzung: C++-Templates sind Makros. Jede C++-Template-Instanzierung erzeugt komplett neuen Binärcode. Java-Generics sind ein komplizierter Versuch, sowas wie den id-Typ aus Objective-C, typsicher nach Java zu portieren. Java-Generics verwenden den gleichen Binärcode für alle Parametertypen. Deswegen braucht man Java-Generics auch nicht zu instanzieren, wie man das früher® mit C++-Templates machen musste. Swift-Generics liegen irgendwo zwischen diesen beiden Ansätzen.

    In dem Punkt, das beide Ansätze bescheiden sind, gebe ich dir hingegen vollkommen recht. ;)
    „Meine Komplikation hatte eine Komplikation.“
  • Ich glaube, jetzt habe ich's soweit verstanden.

    Ein Post auf Stackoverflow und Rumprobieren über den Zwischenschritt einer freien generischen Funktion war wohl nur das letzte Quentchen, das noch gefehlt hatte.

    macmoonshine schrieb:

    tsunamix schrieb:

    Mein Punkt ist ja, daß man das für Float und Double zweimal implementieren müßte.
    Genau. Das ist auch mein Punkt.
    Wenn du eine generische Methode haben möchtest, muss diese wissen, das der Parametertyp:
    1. Das Protokoll FloatLiteralConvertible implementiert.
    2. Die Konstante M_PI kennt.
    3. Die Operatoren * und / besitzt. Durch entsprechende Konstanten kann man sich notfalls einen Operator schenken. Aber das Grundproblem bleibt.

    Das Ganze funktioniert nicht aufgrund der uneinheitlichen Operatoren. Von Schönheit ganz zu schweigen. ;)

    FloatLiteralConvertible hilft hier nicht weiter. Damit hatte ich aber auch zuerst wüst und ohne Sinn und Verstand experimentiert.^^

    Anscheinend muß man _alle_ Methoden, die die generische Instanz verwenden soll, selbst im Protokoll deklarieren, d. h. auch alle Operatoren und Initializer (als Cast), um links und rechts des Operators auch auf den erwarteten gemeinsammen Nenner zu kommen. *ürks*

    Jetzt kann ich jedenfalls nachvollziehen, warum Du mit den Operatoren angekommen bist. ;)

    macmoonshine schrieb:

    Das Protokoll muss dann ja die Punktrechnungsoperatoren von Float und Double kennen. Jene sind aber als Funktionen außerhalb eines Protokolls deklariert. Das Protokoll müsste sie aber vereinheitlicht nachdeklarieren:

    Quellcode

    1. import Foundation
    2. protocol Degreeable {
    3. func *(lhs: Self, rhs: Self) -> Self
    4. func /(lhs: Self, rhs: Self) -> Self
    5. }
    6. extension Degreeable {
    7. var degree: Double {
    8. return self * 180.0 / M_PI
    9. }
    10. }
    11. extension Double: Degreeable {
    12. }
    13. let d: Double = 1
    14. print(d.degree)
    Alles anzeigen

    Geht aber nicht

    So funktioniert's dann:

    Quellcode

    1. import Darwin
    2. protocol Degreeable {
    3. func *(lhs: Self, rhs: Self) -> Self
    4. func /(lhs: Self, rhs: Self) -> Self
    5. init(_ v: Double)
    6. }
    7. extension Degreeable {
    8. var degree: Self {
    9. return self * Self(180.0) / Self(M_PI)
    10. }
    11. }
    12. extension Double: Degreeable { }
    13. extension Float: Degreeable { }
    14. let d = M_PI.degree // 180
    15. let f = Float(M_PI).degree // 180
    Alles anzeigen

    Über die Praxis(un)tauglichkeit in diesem Fall sind wir uns alle ja schon längst einig. Ich wollte ja nur das Ob und Wie geklärt haben. Das ist jetzt erledigt.

    Danke allen Beteiligten für den Input.^^

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von torquato ()

  • macmoonshine schrieb:

    Amin Negm-Awad schrieb:

    Und sein Ansatz verhindert ja nicht die doppelte Implementierung.
    Wenn es funktionieren würde, was es aber nicht tut, müsste man die Methode degree nur einmal implementieren.
    Wieso? Funktioniert doch. Siehe Post oben. ;)

    macmoonshine schrieb:

    Der Overhead dafür ist natürlich indiskutabel.
    Welcher 'Overhead'?

    Beim Code-Schreiben, -Verstehen und wohl auch -Warten würde ich Dir sicherlich recht geben. Schon alleine, weil es offensichtlich gar nicht so trivial war auf die Lösung zu kommen. QED.

    Zur Laufzeit würde ich da keinen Overhead erwarten. Werden da nicht einfach zwei Methoden für Float und Double generiert, die man sonst eh selbst hätte schreiben müssen, und wo dann sowas wie Self(180.0) / Self(M_PI) jeweils in entsprechende Konstanten wegoptimiert wird?

    *grübel*
  • macmoonshine schrieb:

    Ich sehe da einen Unterschied in der Umsetzung: C++-Templates sind Makros. Jede C++-Template-Instanzierung erzeugt komplett neuen Binärcode. Java-Generics sind ein komplizierter Versuch, sowas wie den id-Typ aus Objective-C, typsicher nach Java zu portieren. Java-Generics verwenden den gleichen Binärcode für alle Parametertypen. Deswegen braucht man Java-Generics auch nicht zu instanzieren, wie man das früher® mit C++-Templates machen musste. Swift-Generics liegen irgendwo zwischen diesen beiden Ansätzen.
    C++ instantiert quasi Sourcecode, wobei das ja verborgen ist.

    Java produziert einheitlichen Bytecode, der bei dessen Ausführung dann auf den Zieltypen instantiert wird. Das ist zwar ein netter Trick, dennoch wird duplizierter Code durch einen Compiler erzeugt.

    Swift produziert ganz gewiss auch typabhängigen Code, da Swift keine dynamische Nachrichtenbindung kann. Es gibt keine Nachrichten in Swift.

    Alle drei Ansätzen haben es gemein, dass sie für jeden Typen eigenen Code porduzieren. Es gibt also eine Vorlage (Template), die instantiert wird. Alles andere ist Marketing.

    Objective-C löst das grundsätzlich anders.

    Es kann auch gar nicht anders sein. Dieselbe Codezeile muss in Abhängigkeit des Typen des Argumentes bei einem Call an verschiedene Stellen springen. Dafür gibt es zwei Möglichkeiten:

    * Du hast ein Stück Code, dass die richtige Stelle heraussucht, wenn es darauf ankommt. Das ist eben späte Bindung und verlangt eine dyanmische Programmiersprache (Objective-C).

    * Oder aber du hast das nicht, dann muss die Zielstelle irgendwo im Code bezeichnet sein. Da sie typabhängig unterschiedlich ist, musst du entsprechend verschiedene Bezeichnungen haben. Das kann dann nicht mehr ein Code sein (C++, Java, Swift).

    Als Zwischending könnte man sich nur vorstellen, das die in einem Template verwendeten Calls notiert werden und eine Indirektion durch eine Look-Up-Tabelle nur für diese Funktionen erzeugt wird. Sozusagen "späte Bindung light" mit einem RTE en miniature.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von Amin Negm-Awad ()

  • Amin Negm-Awad schrieb:

    Java produziert einheitlichen Bytecode, der bei dessen Ausführung dann auf den Zieltypen instantiert wird. Das ist zwar ein netter Trick, dennoch wird duplizierter Code durch einen Compiler erzeugt.
    Das ist nicht richtig. Java dupliziert auch keinen Bytecode oder erzeugt speziellen, typabhängigen Code, weswegen sich Java-Generics beispielsweise auch nicht mit den Standarddatentypen int, float usw. nutzen lassen. Ich kann aber in beispielsweise eine generische Sammlung reinpacken, was ich will:

    Quellcode

    1. List<Integer> li = new java.util.ArrayList<Integer>();
    2. List l = li;
    3. l.add(Math.PI);
    4. System.out.println("pi = " + li.get(0)); // kein Fehler



    Bei einem Java-Generic müssen alle verwendeten Methoden in der Klasse des Parametertypen deklariert sein. Ich muss dem Generic auch mitteilen, wenn ich irgendwelche speziellen Methoden aufrufen möchte, z. B.:

    Quellcode

    1. public class Nonsense<T extends Degreeable> {
    2. // ...
    3. }
    Damit weiß der Compiler, welche Methoden der Parametertyp besitzt und kann die entsprechenden Aufrufe erzeugen. Das ist in Java ja auch kein Problem, da diese angeblich pointerfreie Sprache Objekte immer über Zeiger referenziert.

    Der Typ ist ja dem Generic durch die extends-Klausel bekannt, weswegen der Compiler bereits den richtigen Methodenaufruf erzeugen kann, ohne die konkrete Ausprägung des Parametertyps zu kennen. Das ist ja letztendlich nichts anderes als bei der Verwendung eines Interfaces als Parametertyp in einer gewöhnlichen Methode:



    Quellcode

    1. public void doNonsense(Degreeable inValue) {
    2. Degreeable d = inValue.asDegree();
    3. // ...
    4. }
    Generics in Java haben letztendlich nur den Sinn, Casts im Sourcecode zu vermeiden. Das sieht man auch daran, dass beispielsweise die ganzen Collection-Generics in Java 1.5 aus den ungenerischen Interfaces und Klassen aus 1.4 entstanden sind. Damit aber der alte Code nicht bricht, versteht der Compiler den Code auch, wenn man die spitzen Klammern weglässt.
    „Meine Komplikation hatte eine Komplikation.“
  • Ich habe ja auch nicht gesagt, dass er Bytecode dupliziert. Ganz im Gegenteil: "Java produziert *einheitlichen* Bytecode, …" Ich habe gesagt, dass aus dem Bytecode mehrfacher Code (dann also Maschinencode) produziert wird.

    Die unterschiedliche Behandlung der Typisierung bei einem Template hat damit überhaupt nichts zu tun. Dies dient der Source-Verifikation, nicht der Code Generierung. (Ein ähnliches System war ja für C++ 11 geplant und ist immer noch im Ring mit Concepts. – Ohne dass man die Code-Generierung für Templates anfassen müsste.) Zwar kann man in Java Typeinschränkungen formulieren (<T extends Whatever>), man muss das aber nicht und es ist vom Standpunkt des Programmierers auch nicht immer möglich.

    Der Compiler weiß zwar, welche Methoden benutzt werden, nur hilft ihm das nicht die Bohne bei der Codeerzeugung, weil eine Methode bei Klasse A an anderer Stelle implementiert sein kann als bei Klasse B. Bedenke bitte, dass bei statisch bindenden Sprachen der Call nicht mehr symbolisch über den Methodennamen erfolgt, der Methodenname also völlig irrelevant ist. Er ist bereits auf einer Per-Klassen-Basis in einen Index übersetzt, der für eine gleich benannte Methode bei Typ A anders lauten kann als bei Typ B. Da der Index aber im Code steht, muss entsprechend für Typ A ein anderer Code existieren als für Typ B. (Java kann das auch anders, weshalb es dann in einem solchen Falle dynamisch bindet. Das ist aber nicht das System für Generics.) Diese Auflösung des Namens in einen INdex funktioniert nämlich nur bei Ableitung, weil die Subklasse ihre Basisklasse nie einschränken kann.

    Dass das bei Java eine Stufe später erfolgt, ist Implementationsdetail. Der Programmierer merkt überhaupt nicht, ob der Bytecode noch generisch ist und daraus typabhängiger Zielcode erzeugt wird oder ob der Bytecode bereits typabhängig ist. Entscheidund und allen gemein ist, dass der Programmierer eine generisch formulierte Vorlage (aka Template) formuliert, aus der an irgendeiner Stelle in eine typabhängige Variante produziert wird. Das geschieht bei dynamischer Bindung nie.

    Jedes Templatesystem dient dazu, Casts bei dem *Anwender* des Templates zu vermeiden. Ich kann auch in C++ void-Zeiger haben, die ich bei Bedarf auf den entsprechenden Zieltypen zu casten. (So programmiert man generisch in C – ganz ohne Templates und dynamische Bindung.) Es geht nicht um denjenigen, der durch "ArrayList<Whatever>" den Cast erspart, sondern um denjenigen, der ArrayLIst programmiert. Es geht nicht um die Übersetzung von der Nutzung von ArrayList<Whatever> , sondern um die Übersetzung von ArrayList<T> selbst.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von Amin Negm-Awad ()

  • Amin Negm-Awad schrieb:

    Ich habe ja auch nicht gesagt, dass er Bytecode dupliziert. Ganz im Gegenteil: "Java produziert *einheitlichen* Bytecode, …" Ich habe gesagt, dass aus dem Bytecode mehrfacher Code (dann also Maschinencode) produziert wird.
    Und ich habe gesagt, dass Java weder mehrfachen Byte- noch Maschinencode erzeugt. In dem Beispiel kann die Runtime nur bei Ausführung des Codes wissen, welchen konkreten Typ li.get(0) hat, und sie kann somit auch erst bei der Ausführung ermitteln, welche toString()-Methode sie für die Berechnung des Ausdrucks "pi = " + li.get(0) aufrufen muss.
    „Meine Komplikation hatte eine Komplikation.“
  • Wenn sie das erst zur Laufzeit ermittelt und dann noch anhand des Methodennamens, wäre es dynamische Bindung, oder?

    Sie erzeugt zur Laufzeit einen Zielcode für den entsprechenden Typen.

    Wikipedia schrieb:

    Der Quelltext eines C++-Templates muss für den Anwender (d. h. beim Einsetzen des Typparameters) verfügbar sein, während ein generischer Java-Typ auch als übersetzter Bytecode veröffentlicht werden kann. Für verschiedene aktuelle Typparameter produziert der Compiler duplizierten Zielcode.
    Wie dem auch sei: Das ist wie gesagt alles Implementierungsdetail, eine Änderung der Strategie würde der Programmierer gar nicht bemerken. Entscheidend ist, dass aus Sicht des Programmierers ein Stück Code geschrieben wird, dass so in seiner Programmiersprache nicht zulässig wäre: Es fehlt der Typ, der stets gefordert wird. Es ist also ein zusätzliches System erforderlich, das eine Konkretisierung abbildet.

    Das wäre sogar dann so, wenn die Laufzeit tatsächlich jeden Call über dynamische Bindung auflöst. Das ändert nichts für den Programmierer daran, dass er in statisch bindenden Programmiersprachen einen Typen angebenen *muss* und dies die Formulierung generischen Codes entgegensteht. Demensprechend benötige ich zusätzlich zu dem System für typkonkrete Formulierung des Codes ein zweites (Meta-)System zur Formulierung der Typkonkretisierung: Template, schon im Wortsinne. Ob ich dabei Typeconstraints, Concepts oder gar nichts angeben kann oder muss (IIRC Strongtallk), ändert daran nichts.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Amin Negm-Awad ()

  • Amin Negm-Awad schrieb:

    Wie dem auch sei: Das ist wie gesagt alles Implementierungsdetail, eine Änderung der Strategie würde der Programmierer gar nicht bemerken.
    Wenn Java auf den C++-Template-Mechanismus umschwenken würde, würde es der Programmierer wahrscheinlich merken. Dann könnte er zwar Templates erstellen, der Parameter nichts von verwendeten Parametertypen wissen müssen. Andererseits wäre das Beispiel von oben so nicht mehr möglich. Das C++-Analogon sähe ungefähr so aus:

    Quellcode

    1. vector<Integer> li = new vector<Integer>(); // OK
    2. vector &l(li); // Fehler, Template ohne Argument nicht möglich
    3. l.push_pack(Float(M_PI)); // Folgefehler und Fehler (in einen vector<Integer> kann ich in C++ niemals einen Float einfügen).
    4. System.out.println("pi = " + li[0].str()); // Immer Aufruf von Integer::str()
    Selbst wenn C++ eine generelle Oberklasse wie Java (java.lang.Object) hätte, würde der C++-Code wegen den Zeilen 2 und 3 nicht funktionieren. Wenn man stattdessen mit vector<Integer *> und vector<Object *> arbeitete, könnte man vielleicht den Code mit einigen bösen Casts zum Laufen bringen. Darauf hatte ich auch schon so halb hingewiesen:


    macmoonshine schrieb:

    Das ist in Java ja auch kein Problem, da diese angeblich pointerfreie Sprache Objekte immer über Zeiger referenziert.

    Das sich der C++- und der Java Mechanismus von Objective-C mit der Verwendung des Vorlagenmusters unterscheiden, ist klar. Der Unterschied zwischen C++ und Java geht aber über ein einfaches Implementierungsdetail hinaus.
    „Meine Komplikation hatte eine Komplikation.“
  • Ich meinte nicht eine Änderung der Struktur der Templates für den Programmierer, sondern die Änderung der Strategie für die Templateauflösung.

    Amin Negm-Awad schrieb:

    Der Programmierer merkt überhaupt nicht, ob der Bytecode noch generisch ist und daraus typabhängiger Zielcode erzeugt wird oder ob der Bytecode bereits typabhängig ist. Entscheidund und allen gemein ist, dass der Programmierer eine generisch formulierte Vorlage (aka Template) formuliert, aus der an irgendeiner Stelle in eine typabhängige Variante produziert wird. Das geschieht bei dynamischer Bindung nie.

    Amin Negm-Awad schrieb:

    Das ist wie gesagt alles Implementierungsdetail, eine Änderung der Strategie würde der Programmierer gar nicht bemerken.
    Ich habe daher auch nicht gesagt, dass sich die Templateimplementierung in C++ und Java (und Swift) nicht unterscheiden. Ich habe gesagt, dass alle Implementierung eine Vorlage (Template) in einer zusätzlichen (Meta-)Sprache formulieren, de dann irgendwann typkonkretisiert wird und daher die Bezeichnung Template genau richtig für alle Implementierungen ist.

    Die Bezeichnung "Generic" bezieht sich indessen auf jede Art der typunabhängigen Programmierung einschließlich der dynamischen Bindung. Sie spiegelt also etwas vor, was nicht vorhanden ist.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Amin Negm-Awad ()