Nehmen wir an, ich baue einen Parser für eine domänenspezifische Sprache in F #.
Ich habe eine diskriminierte Vereinigung definiert, um Ausdrücke darzustellen:
%Vor% Nun habe ich einen AST vom Typ Expression
erstellt und möchte Code dafür generieren.
Ich habe eine Funktion, die Inferenz und Typprüfung für einen Ausdruck eingibt.
Es ist definiert wie
%Vor%Und ich habe eine andere Funktion, um Code zu erzeugen, der einem ähnlichen Muster folgt: Nimm einen Ausdruck, schreibe Mustervergleichsanweisungen für jedes Element in der Vereinigung.
Meine Frage ist: Ist das der idiomatische Weg, es in F # zu machen?
Es scheint mir, dass es sauberer wäre, wenn jedes Mitglied der Gewerkschaft sein eigenes InferType
und GenerateCode
lokal damit definieren würde.
Wenn ich C # verwenden würde, würde ich eine abstrakte Basisklasse namens Expression
mit virtuellen Methoden für InferType
und GenerateCode
definieren und sie dann in jeder Unterklasse überschreiben.
Gibt es einen anderen Weg, dies zu tun?
Es scheint mir so zu sein sauberer wenn jedes Mitglied der Gewerkschaft definiert sein eigenes
InferType
undGenerateCode
lokal damit.
Ich glaube, Sie meinen "vertrauter", nicht "sauberer".
Wirklich, ist Ihr Idealfall, dass Sie Code-Implementierung in 10 verschiedenen Klassen verteilt?
Es besteht definitiv eine grundsätzliche Spannung zwischen der Frage, ob Sie Dinge "nach Art" oder "nach Operation" gruppieren wollen. Der übliche OO-Weg ist "nach Typ", während der FP-Weg (funktionale Programmierung) "nach Operation" ist.
Im Fall eines Compilers / Interpreters (oder der meisten Dinge, die sich in OO stark auf das Visitor-Muster beziehen) denke ich, dass "by operation" die natürlichere Gruppierung ist. Der Code-Generator für If
und And
und Or
kann ein wenig gemeinsam haben; die Typenprüfer der verschiedenen Knoten werden in ähnlicher Weise Gemeinsamkeiten aufweisen; Wenn Sie einen Pretty-Printer erstellen, wird es wahrscheinlich Formatierungsroutinen geben, die allen Nice-Pretty-Printing-Implementierungen gemein sind. Im Gegensatz dazu haben das Drucken, die Typenkontrolle und das Codegennen von IfElse
wirklich gar nichts miteinander zu tun. Warum sollten Sie diese also in einer Klasse IfElse
gruppieren?
(Um deine Fragen zu beantworten: Ja, das ist idiomatisch. Gibt es einen anderen Weg - ja, du kannst es genauso machen wie in C #. Ich denke, du wirst feststellen, dass du viel weniger glücklich mit dem C # -Weg bist, und dass der Code auf diese Weise auch 2-3x größer ist, ohne Nutzen.
Als OCaml-Programmierer würde ich sagen, dass dies völlig idiomatisch ist. Übrigens gibt dies eine bessere Trennung von Belangen, als wenn Sie eine Klassenhierarchie mit Klassenmethoden geschrieben hätten. Sie würden eine ähnliche Modularität in einer OO-Sprache mit einem InferType-Besucher erhalten, aber es wäre viel mehr Code.
Die andere Sache, die Sie normalerweise in einer funktionalen Sprache tun könnten, besteht darin, eine fold
-Operation für den Datentyp zu definieren und dann die typechecking- und code-erzeugenden Funktionen in Bezug auf die Faltung zu definieren. In diesem speziellen Fall bin ich mir nicht sicher, ob es dir viel einbringt, weil die Fold-Funktion so viele Argumente haben würde, dass es nicht besonders leicht zu verstehen sein wird:
Tags und Links f#