Ich schreibe einen grafischen Editor für ein "Modell" (d. h. eine Sammlung von Kästchen und Zeilen mit irgendeiner Art von Semantik wie UML, deren Details hier keine Rolle spielen). Ich möchte also eine Datenstruktur, die das Modell darstellt, und ein Diagramm, bei dem eine Bearbeitung des Diagramms eine entsprechende Änderung im Modell bewirkt. Wenn zum Beispiel ein Modellelement einen Text in einem Attribut hat und ich den Text im Diagramm bearbeite, möchte ich, dass das Modellelement aktualisiert wird.
Das Modell wird wahrscheinlich als Baum dargestellt, aber ich möchte, dass der Diagrammeditor so wenig wie möglich über die Modelldarstellung weiß. (Ich verwende das Diagramme -Framework, so dass die Zuordnung beliebiger Informationen zu einem grafischen Element einfach ist). Es wird wahrscheinlich eine "Modell" -Klasse geben, um die Schnittstelle zu kodieren, wenn ich nur herausfinden kann, was das sein soll.
Wenn ich das in einer imperativen Sprache machen würde, wäre es einfach: Ich hätte nur eine Referenz vom grafischen Element im Diagramm zurück zum Modellelement. Theoretisch könnte ich das trotzdem tun, indem ich das Modell aus einer riesigen Sammlung von IORefs aufbaute, aber das würde ein Java-Programm in Haskell schreiben.
Offensichtlich wird jedem grafischen Element eine Art Cookie zugeordnet, die die Aktualisierung des Modells ermöglicht. Eine einfache Antwort wäre, jedem Modellelement einen eindeutigen Bezeichner zu geben und das Modell in einer Data.Map-Nachschlagetabelle zu speichern. Dies erfordert jedoch eine signifikante Buchführung, um sicherzustellen, dass keine zwei Modellelemente die gleiche Kennung erhalten. Es scheint mir auch eine "straff getippte" Lösung zu sein; Sie müssen Fälle behandeln, in denen ein Objekt gelöscht wird, aber an anderer Stelle gibt es einen baumelnden Verweis darauf, und es ist schwierig, etwas über die interne Struktur des Modells in Ihren Typen zu sagen.
Auf der anderen Seite klingen Olegs Schriften über Reißverschlüsse mit mehreren Löchern und Cursors mit klarem transaktionalem Teilen wie eine bessere Option, wenn ich es nur verstehen könnte. Ich bekomme die Grundidee von List- und Tree-Zippern und der Differenzierung einer Datenstruktur. Wäre es möglich, dass jedes Element in einem Diagramm einen Cursor in einen Reißverschluss des Modells hält? Wenn also eine Änderung vorgenommen wird, kann sie dann allen anderen Cursorn übergeben werden. Einschließlich Baumoperationen (z. B. Verschieben eines Teilbaums von einem Ort zu einem anderen)?
Es würde mir an dieser Stelle besonders helfen, wenn es eine Art Tutorial über abgegrenzte Fortsetzungen gäbe, und eine Erklärung, wie sie Olegs Multi-Cursor-Reißverschlüsse zum Laufen bringen, die etwas weniger steil sind als Olegs Beiträge?
Ich denke, Sie arbeiten derzeit an einem Entwurf, bei dem jeder Knoten in der Modellhierarchie durch ein separates grafisches Widget dargestellt wird und jedes dieser Widgets das Modell unabhängig aktualisieren kann. Wenn ja, glaube ich nicht, dass ein Multi-Loch-Reißverschluss sehr praktisch sein wird. Das Problem ist, dass die Komplexität des Reißverschlusses mit der Anzahl der Löcher wächst, die Sie unterstützen möchten. Wenn Sie mehr als 2 Begriffe bekommen, wird die Größe des Reißverschlusses ziemlich groß. Aus der Sicht der Differenzierung ist ein 2-Loch-Reißverschluss ein Reißverschluss über 1-Loch-Reißverschlüssen, so dass die Komplexität durch den Einsatz der Kettenregel wächst.
Stattdessen können Sie eine Idee von MVC ausleihen. Jeder Knoten ist weiterhin mit einem Widget verknüpft, kommuniziert jedoch nicht direkt. Vielmehr durchlaufen sie einen Zwischenregler, der einen einzigen Reißverschluss unterhält. Wenn Widgets aktualisiert werden, benachrichtigen sie den Controller, der alle Aktualisierungen serialisiert und den Reißverschluss entsprechend modifiziert.
Die Widgets benötigen weiterhin eine Art von Bezeichnern, um auf Modellknoten zu verweisen. Ich habe herausgefunden, dass es am einfachsten ist, den Pfad des Knotens zu verwenden, z. [0]
für das Stammverzeichnis, [1,0]
für das zweite untergeordnete Element des Stammverzeichnisses usw. Dies hat einige Vorteile. Es ist einfach, den Knoten zu bestimmen, auf den sich ein Pfad bezieht, und es ist auch einfach für einen Zipper, den kürzesten Pfad vom aktuellen Standort zu einem bestimmten Knoten zu berechnen. Für eine Baumstruktur sind sie auch bis zum Löschen und Wiedereinfügen einzigartig. Auch das ist normalerweise kein Problem, denn wenn der Controller benachrichtigt wird, dass Knoten gelöscht werden sollen, kann er die entsprechenden Widgets löschen und zugehörige Aktualisierungen ignorieren. Solange die Lebensdauer des Widgets an die Lebensdauer jedes Knotens gebunden ist, ist der Pfad ausreichend eindeutig, um Änderungen zu erkennen.
Bei Baumoperationen würde ich wahrscheinlich grafische Widgets zerstören und neu erstellen.
Als Beispiel habe ich einen Code, der so etwas macht . In diesem Modell gibt es keine separaten Widgets für jeden Knoten, vielmehr rendere ich alles mithilfe von Diagrammen und frage dann das Diagramm basierend auf der Klickposition ab, um den Pfad in das Datenmodell zu bekommen. Es ist bei weitem nicht vollständig, und ich habe es eine ganze Weile nicht angeschaut, also könnte es nicht funktionieren, aber der Code könnte Ihnen einige Ideen geben.
Tags und Links haskell data-structures zipper