Frage

Gott, ich hasse den Begriff "Code -Geruch", aber ich kann mir nichts Genauereres vorstellen.

Ich entwerfe eine hochrangige Sprache und einen Compiler für Whitespace In meiner Freizeit, um über Compiler -Konstruktion, Sprachdesign und funktionale Programmierung zu erfahren (Compiler wird in Haskell geschrieben).

Während der Code-Erzeugungsphase des Compilers muss ich "Status" -ISH-Daten beibehalten, während ich den Syntaxbaum durchquere. Wenn ich beispielsweise Flusskontrollanweisungen zusammenstellte, muss ich eindeutige Namen generieren, zu denen die Beschriftungen springen können (Etiketten, die von einem Zähler generiert werden, der eingereicht, aktualisiert und zurückgegeben wird, und der alte Wert des Zählers darf nie wieder verwendet werden). Ein weiteres Beispiel ist, wenn ich auf Inline-String-Literale im Syntaxbaum stoße, müssen sie dauerhaft in Haufenvariablen umgewandelt werden (in der Whitespace werden Zeichenfolgen am besten auf dem Haufen gespeichert). Ich wickle derzeit das gesamte Codegenerierungsmodul in die staatliche Monad ein.

Mir wurde gesagt, dass das Schreiben eines Compilers ein Problem ist, das gut für das funktionale Paradigma geeignet ist, aber ich finde, dass ich dies auf die gleiche Weise entworfen habe Haskell mit staatlichen Monaden).

Ich möchte lernen, wie man in Haskell denkt (eher im funktionalen Paradigma) - nicht in C mit Haskell -Syntax. Sollte ich wirklich versuchen, die Verwendung der staatlichen Monadin zu beseitigen/zu minimieren, oder ist es ein legitimes funktionelles "Designmuster"?

War es hilfreich?

Lösung

Ich würde sagen, dass Staat im Allgemeinen kein Codegeruch ist, solange er klein und gut kontrolliert wird.

Dies bedeutet, dass die Verwendung von Monaden wie Zustand, ST oder maßgeschneiderten oder nur eine Datenstruktur, die staatliche Daten enthält, die Sie an einige Orte übergeben, keine schlechte Sache ist. (Eigentlich sind Monaden nur Hilfe, um genau das zu tun!) Es ist jedoch ein schlechter Geruch.

Ein ziemlich klares Beispiel dafür war, als mein Team an unserem Eintrag für die arbeitete ICFP -Programmierwettbewerb 2009 (Der Code ist unter git: //git.cynic.net/haskell/icfp-contest-2009 verfügbar). Am Ende hatten wir verschiedene modulare Teile:

  • VM: Die virtuelle Maschine, die das Simulationsprogramm leitete
  • Controller: Verschiedene verschiedene Routinensätze, die die Ausgabe des Simulators lesen und neue Steuereingänge generierten
  • Lösung: Erzeugung der Lösungsdatei basierend auf der Ausgabe der Controller
  • Visualisierer: Mehrere verschiedene Routinensätze, die sowohl die Eingangs- als auch die Ausgabeports lesen und eine Art Visualisierung oder Protokoll des Fortschritts der Simulation erzeugten

Jedes von diesen hat seinen eigenen Zustand, und alle interagieren auf verschiedene Weise durch die Eingabe- und Ausgangswerte der VM. Wir hatten mehrere verschiedene Controller und Visualisierer, von denen jede ihre eigene Art von Zustand hatte.

Der entscheidende Punkt hier war, dass die Interna eines bestimmten Staates auf ihre eigenen Module beschränkt waren und jedes Modul nichts über die Existenz des Staates für andere Module wusste. Ein bestimmter Satz staatlicher Code und Daten war im Allgemeinen nur wenige Dutzend Zeilen lang, mit einer Handvoll Datenelemente im Zustand.

All dies wurde in einer kleinen Funktion von etwa einem Dutzend Zeilen zusammengeklebt, die keinen Zugang zu den Internalen eines der Staaten hatten und die lediglich die richtigen Dinge in der richtigen Reihenfolge bezeichneten Menge an externen Informationen zu jedem Modul (natürlich zusammen mit dem vorherigen Zustand des Moduls).

Wenn der Zustand so begrenzt verwendet wird und das Typsystem Sie nicht versehentlich verändert, ist es recht einfach zu handhaben. Es ist eine der Schönheiten von Haskell, dass Sie dies tun können.

Eine Antwort sagt: "Verwenden Sie keine Monaden." Aus meiner Sicht ist dies genau rückwärts. Monaden sind eine Kontrollstruktur, die Ihnen unter anderem helfen kann, die Menge an Code zu minimieren, die den Zustand berührt. Wenn Sie sich monadische Parser als Beispiel ansehen, müssen der Zustand des Analyse (dh der analysierte Text analysiert, wie weit man sich angesammelt hat usw.) durch jeden im Parser verwendeten Kombinator laufen müssen . Es wird jedoch nur wenige Kombinatoren geben, die den Staat tatsächlich direkt manipulieren. Alles andere verwendet eine dieser wenigen Funktionen. Auf diese Weise können Sie klar und an einem Ort eine kleine Menge Code sehen, die den Status ändern kann, und leichter zu begründen, wie er geändert werden kann, was wiederum erleichtert wird.

Andere Tipps

Ich habe mehrere Compiler in Haskell geschrieben, und eine staatliche Monadin ist eine vernünftige Lösung für viele Compiler -Probleme. Aber Sie möchten es abstrakt halten-machen Sie nicht klar, dass Sie eine Monate verwenden.

Hier ist ein Beispiel aus dem Glasgow Haskell Compiler (was ich getan habe nicht schreiben; Ich arbeite nur um ein paar Kanten), in denen wir Kontroll-Flow-Diagramme erstellen. Hier sind die grundlegenden Möglichkeiten, Grafiken zu erstellen:

empyGraph    :: Graph
mkLabel      :: Label -> Graph
mkAssignment :: Assignment -> Graph  -- modify a register or memory
mkTransfer   :: ControlTransfer -> Graph   -- any control transfer
(<*>)        :: Graph -> Graph -> Graph

Aber wie Sie festgestellt haben, ist die Aufrechterhaltung eines Angebots an einzigartigen Etiketten bestenfalls mühsam, sodass wir auch diese Funktionen bieten:

withFreshLabel :: (Label -> Graph) -> Graph
mkIfThenElse :: (Label -> Label -> Graph) -- branch condition
             -> Graph   -- code in the 'then' branch
             -> Graph   -- code in the 'else' branch 
             -> Graph   -- resulting if-then-else construct

Das Ganze Graph Die Sache ist ein abstrakter Typ, und der Übersetzer konstruiert nur fröhlich Diagramme in rein funktionaler Weise, ohne sich bewusst zu sein, dass etwas Monadisches vor sich geht. Wenn der Diagramm endlich konstruiert wird, können wir ihm einen algebraischen Datentyp erstellen, um ihn in einen algebraischen Datentyp zu verwandeln, wir bieten ihm eine Versorgung mit einzigartigen Beschriftungen, führen die Zustandsmonade aus und ziehen die Datenstruktur heraus.

Der Staat Monad ist darunter versteckt; Obwohl es dem Kunden nicht ausgesetzt ist, die Definition von Graph ist so etwas wie This:

type Graph = RealGraph -> [Label] -> (RealGraph, [Label])

oder ein bisschen genauer

type Graph = RealGraph -> State [Label] RealGraph
  -- a Graph is a monadic function from a successor RealGraph to a new RealGraph

Mit dem staatlichen Monad, der hinter einer Abstraktionsebene versteckt ist, stinkend er überhaupt nicht!

Hast Du Dir angesehen Grammatiken zuschreiben (AG)? (Weitere Informationen zu Wikipedia und ein Artikel im Monad -Leser)?

Mit AG können Sie hinzufügen Attribute zu einem Syntaxbaum. Diese Attribute sind in getrennt synthetisiert und vererbt Attribute.

Synthetisierte Attribute sind Dinge, die Sie aus Ihrem Syntaxbaum generieren (oder synthetisieren). Dies kann der generierte Code oder alle Kommentare sein oder was auch immer Sie sonst noch interessieren.

Geerbte Attribute werden in Ihren Syntaxbaum eingegeben, dies kann die Umgebung oder eine Liste von Etiketten sein, die während der Codegenerierung verwendet werden sollen.

An der Utrecht University verwenden wir die Attribut -Grammatiksystem (UUAGC) Compiler schreiben. Dies ist ein Pre-Processor, der Haskell-Code generiert (.hs Dateien) aus dem bereitgestellten .ag Dateien.


Wenn Sie jedoch noch Haskell lernen, ist dies vielleicht nicht die Zeit, um eine weitere Abstraktionsebene darüber zu lernen.

In diesem Fall können Sie beispielsweise die Art von Code, die Grammatiken für Sie generieren, manuell schreiben:

data AbstractSyntax = Literal Int | Block AbstractSyntax
                    | Comment String AbstractSyntax

compile :: AbstractSyntax -> [Label] -> (Code, Comments)
compile (Literal x) _      = (generateCode x, [])
compile (Block ast) (l:ls) = let (code', comments) = compile ast ls
                             in (labelCode l code', comments)
compile (Comment s ast) ls = let (code, comments') = compile ast ls
                             in (code, s : comments')

generateCode :: Int -> Code
labelCode :: Label -> Code -> Code

Es ist möglich, dass Sie einen ordnungsgemäßen Funkartor anstelle eines Monades wünschen:

http://www.haskell.org/haskellwiki/applicative_functor

Ich denke, das Originalpapier erklärt es jedoch besser als das Wiki:

http://www.soi.city.ac.uk/~ross/papers/applicative.html

Ich glaube nicht, dass die Verwendung des staatlichen Monades ein Codegeruch ist, wenn er den Zustand modellierte.

Wenn Sie den Zustand durch Ihre Funktionen faden müssen, können Sie dies explizit tun, den Zustand als Argument nutzen und in jeder Funktion zurückgeben. Der Staat Monad bietet eine gute Abstraktion: Sie gibt den Staat für Sie weiter und bietet viele nützliche Funktionen, um Funktionen zu kombinieren, die Zustand erfordern. In diesem Fall ist die Verwendung des staatlichen Monades (oder Bewerbers) kein Codegeruch.

Wenn Sie jedoch die staatliche Monadin verwenden, um einen imperativen Programm der Programmierung zu emulieren, während eine funktionale Lösung ausreicht, machen Sie die Dinge nur kompliziert.

Im Allgemeinen sollten Sie versuchen, einen Zustand zu vermeiden, wo immer möglich, aber das ist nicht immer praktisch. Applicative Effektiver Code sieht schöner und funktionaler aus, insbesondere der Baumtraversalcode kann von diesem Stil profitieren. Für das Problem der Namensgenerierung gibt es jetzt ein ziemlich schönes Paket: Wertversuche.

Verwenden Sie keine Monaden. Die Kraft der funktionalen Programmierung ist Funktionsreinheit und ihre Wiederverwendung. Es gibt diese Zeitung, die ein Professor von mir einmal geschrieben hat, und er ist einer der Jungs, die beim Bau von Haskell geholfen haben.

Das Papier heißt ""Warum funktionale Programmierungen wichtig sind"Ich schlage vor, Sie lesen es durch. Es ist eine gute Lektüre.

Seien wir vorsichtig mit der Terminologie hier. Staat ist nicht per se schlecht; Funktionale Sprachen haben Zustand. Was ist ein "Code -Geruch", wenn Sie Variablenwerte zuweisen und diese ändern möchten.

Natürlich ist der Haskell State Monad aus genau diesem Grund da-wie bei der I/A können Sie in einem eingeschränkten Kontext unsichere und nicht funktionale Dinge tun.

Also, ja, es ist wahrscheinlich ein Codegeruch.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top