@andere: Wenn ihr euch über Meflins Beitrag beschwert, dann ist das ungerecht, da ich ihn - wie er geschrieben hat - explizit nach Beispielen gefragt habe.
Daher @Meflin: Dank für die Antwort.
Eben genau solche Beispiele. Zum Beispiel was du in Delphi hättest machen müssen, aber durch entsprechende Features (also Traits, Duck Typing, Type Inference, etc.) in einer anderen Sprache übersichtlicher oder einfacher hinbekommen hast.
OK. Fangen wir mal mit Type Inference an. Wie sehen anonyme Funktionen in Delphi aus (Beispiel aus der Doku)?
Delphi-Quellcode:
type
TFuncOfInt = reference to function(x: Integer): Integer;
function MakeAdder(y: Integer): TFuncOfInt;
begin
Result := function(x: Integer)
begin
Result := x + y;
end;
end;
Bitte was? Eigentlich ist das Konzept auch dazu gedacht, Code kürzer und pregnanter schreiben zu können. Aber das ist ja mal gelinde gesagt größtenteils Code Noise. Mit Inferer könnte das ganze logisch völlig äquivalent (und nach wie vor typsicher) z.B. so aussehen:
Delphi-Quellcode:
function MakeAdder(y: Integer): Integer => Integer;
begin
Result := x => x + y;
end;
Ja, diese Umfangheit bei anonymous Functions ist mir auch ein gewisses Gräuel. Da hatten wir auch vor kurzem einen Thread dazu auf der
fpc-devel Mailing List.
Allerdings bin ich auch von der Syntax "param => body" definitiv kein Freund. Eine Variante, die wir uns überlegt haben war (unter der Annahme, dass FPC Type Inference unterstützen würde):
Delphi-Quellcode:
function MakeAdder(y: Integer): TIntegerFunc;
begin
Result := lambda(x) as x + y;
end;
Aktuell gibt es allerdings keinen Plan Type Inference zum Compiler hinzuzufügen, aber damit experimentieren möchte ich auf jeden Fall mal.
Zitat von
Meflin:
Weiter zum Thema AOP. Damit kann ich z.B. mit weniger als 10 Zeilen Code an einer gebündelten Stelle einen kompletten Call Tree Logger implementieren, der z.B. JEDEN Methodencall loggt. Das ganze ist auch wunderbarst optional einkompilierbar, d.h. in der Production-Version ist der Code einfach nicht drin. Ansatzweise sähe das so aus (AspectJ):
Code:
public aspect TraceCalls {
pointcut aCall : call(* *(..));
before() : aCall() {
System.out.println(thisJoinPoint);
}
}
Das einzige was mir da in Delphi bleibt, ist in JEDER Methode einen Log-Aufruf zu implementieren und am besten noch mit ifdefs zu umschließen, damit das ganze auch optional reinkompiliert werden kann. ODer ich brauche ein special-prupose Logging-Tool, das den Code entsprechend instrumentieren kann. Muss ich noch viel mehr dazu sagen?
Ich verstehe durchaus deine Argumentation... irgendwie fehlt mir nur das Verständnis für Aspektorientierte Programmierung (und damit ne Idee, wie ich das in FPC umsetzen könnte)... Ich muss mich wohl mal genauer mit dem Thema beschäftigen.
Zitat von
Meflin:
Weiter zum Thema Traits. Traits sind enorm vielseitig einsetzbar, und deren Mächtigkeit habe ich selbst glaube ich auch noch nicht vollumfänglich erfasst. z.B. kann man damit Multiple-Inheritance-ähnliche Dinge machen, allerdings mit deutlich weniger Brainfuck. Man kann auch einfach beliebige Funktionalität an beliebige Objekte drankleben. z.B. könnte man damit folgendes implementieren (gewürzt mit Duck Typing):
Code:
type ConvertibleToString: { def toString: String }
trait Signature { self => ConvertibleToString
var signature: String = ""
def isSigned: Bool = ...
def sign(key: String) = ... // Implementation über self.toString
}
// und jetzt kann ich alles, was eine toString Methode hat, damit signieren.
val s = new List("a", "b", "c") with Signature
s.sign("secret")
Oha. Ein beliebiges Objekt hat nun plötzlich eine Signatur und wäre somit quasi vor Änderung geschützt (wobei man in echt vermutlich eher auf Serializable gehen sollte, anstelle von toString). Ist in Delphi einfach völlig unmöglich. Was da am nächsten hinkäme, wäre dann, eine Art Service zu haben, der Signaturen für Objekte verwaltet. Aber mal ganz ehrlich: Was davon ist der bessere Code?
Nur zum Verständnis: du sagst quasi pro Instanz welche Traits verwendet werden sollen? Können auch mehrere Traits an einer Instanz gleichzeitig verwendet werden?
Zitat von
Meflin:
Duck Typing ist auch extrem nützlich, wenn man mit Interfaces zu tun hat, die man selbst nicht manipulieren kann - also z.B. alles was Teil der Standardlibrary ist. Ich kann einfach Funktionen definieren, die alles als Input schlucken, was eben die Methoden hat, die ich in dieser Methode brauche - ohne dass dafür dieses Objekt ein explizites Interface implementieren müssten. Sprich ich kann damit ganz leicht Kompatibilität herstellen, wo sie das Typsystem ohne tieferen Sinn "zerstört" hat.
Ok, wenn man es so formuliert klingt das durchaus nützlich.
Auch Pascal hatte seinerzeit innovative Konzepte – z.B. gibt es nested functions, weshalb z.B. der Bedarf für anonyme Funktionen gar nicht so groß ist. Man sollte lieber dieses Konzept ausbauen... nested functions sind z.B. wie gemacht für Closures (und die wiederum passen super zum Sichtbarkeitsprinzip von Pascal):
Delphi-Quellcode:
type
TFuncOfInt = function(x: Integer): Integer;
function MakeAdder(y: Integer): TFuncOfInt;
function Add(x: integer): Integer;
begin
Result := x + y;
end;
begin
Result := Add;
end;
Es ist eigentlich naheliegend, aber soweit ich weiß geht es immer noch nicht.
Das Problem ist hier, dass im aktuellen Fall der Compiler für eine Nested Function einfach den Parent Frame Pointer als weiteren Parameter übergibt. Im Fall von Closures müssen aber die eingefangenen Parameter über die Lebenszeit des Parent FP hinaus aktiv gehalten werden (denke an Interfaces oder Strings). Also müssen spezielle Strukturen angelegt werden, die diese Parameter enthalten und vorzugsweise freigegeben werden, wenn der Closure nicht mehr benötigt wird (was meinst du warum Closures in Delphi intern Interfaces sind?).
Es ist also vergleichsweise teuer einen Closure anzulegen, während eine Nested Function billig ist. Der Compiler müsste also beim Parsen einer Nested Function aufpassen, dass er sich nicht den Weg verbaut die Funktion eventuell als Closure rauszugeben (Stichwort Single Pass Parsing).
Ich sage nicht, dass das unmöglich ist, aber trivial ist es auch nicht. Eine nette Herausforderung also das umzusetzen
Übrigens: FPC unterstützt ein Zwischending, bei dem du eine geschachtelte Funktion an andere Funktionen als Parameter übergeben kannst, aber nur solange du dich in der Parent Funktion befindest. Siehe
hier.
Gruß,
Sven