Welches Algebraische Muster passt zu diesem Baumtyp?

8

Ich habe ein Rätsel für dich,

Ich habe es geschafft, Code zu schreiben, der diese Dinge mit Rekursionsschemata machen würde, aber es ist unglaublich unordentlich und das bedeutet normalerweise, dass ich irgendwo eine nützliche Abstraktion verpasst habe.

Ich entwerfe ein Layout-System für meinen Texteditor Rasa ; Es verwendet sehr ähnliche Splits Art wie Vim. Ich beschloss, die Splits mit einem Baum zu beschreiben; du kannst dir vorstellen es als binärer Baum von entweder vertikalen oder horizontalen Spaltungen mit 'Ansichten' an der Blattknoten. Dies Bild könnte helfen.

Hier ist meine anfängliche Datenstruktur:

%Vor%

Einige der benötigten Operationen sind:

  • %Code% Deutsch: www.doc-o-matic.de/webhelp/TdlgEditEditE.html. Englisch: www.doc-o-matic.com/webhelp/TdlgEditE.html Dabei werden Knoten (oder nicht) in zwei Knoten horizontal oder vertikal geteilt (unter Beibehaltung ihrer Position in Deutsch: der Baum)
  • split :: (View -> Tree View) -> Tree View -> Tree View , die alle Ansichten, die mit dem Prädikat übereinstimmen, durch Entfernen schließt sie vom Baum und ordnen benachbarte Ansichten richtig.
  • %Code%; Ich möchte, dass der Baum ein Funktor ist, damit ich die Ansichten ändern kann.

Einige nette Features: - close :: (View -> Bool) -> Tree View -> Tree View , Setzt eine Ansicht so, dass sie nur dann aktiv ist, wenn die nächste horizontal verbunden ist     Ansicht nach links WAS aktiv

Ich suche nach einer Abstraktion oder einer Reihe von Abstraktionen, die dies ermöglichen würden Funktionalität auf eine saubere Art und Weise. Hier ist mein Denkprozess soweit:

Zuerst dachte ich, ich hätte ein Monoid. Die Identität war der leere Baum, und fmap würde einfach einen weiteren Zweig an den Baum anhängen, aber das funktioniert nicht seit ich zwei Operationen habe: vertikal append und horizontal append und die Operationen sind nicht assoziativ, wenn sie zusammengemischt sind.

Als nächstes dachte ich: "Einige meiner Operationen hängen von ihrem Kontext ab", also habe ich wahrscheinlich einen Comonad. Die Version des Baumes Ich arbeite nicht als Co-Monade, weil ich keinen Wert für focusRight :: Tree View -> Tree View in einem Zweig habe, also habe ich meinen Baum neu strukturiert so:

%Vor%

aber das immer noch Ich habe den Fall nicht behandelt, in dem ein Knoten basierend auf dem, was drin war, "aufgeteilt" wurde. Dies entsprach der Signatur mappend , die mit extract von Monad vereinheitlicht wurde, also hatte ich vielleicht eine Monade? Ich kann Monade für die implementieren ursprüngliche Baumdefinition, kann es aber nicht für meine Comonad-Baumversion herausfinden.

Gibt es eine Möglichkeit, das Beste aus beiden Welten hier zu bekommen? Entgrabe ich mit Comonad / Monad den falschen Baum? Grundsätzlich suche ich nach einer eleganten Möglichkeit, diese Funktionen über meine Datenstruktur zu modellieren. Danke!

Wenn Sie den vollständigen Code sehen möchten, sind die Funktionen hier und der aktuelle Baum ist hier .

    
Chris Penner 19.03.2017, 19:22
quelle

2 Antworten

6

Ich habe es aufgegeben, das in einen Kommentar zu stopfen. Conor McBride hat eine ganze rede und mit Sam Lindley ein großer Teil eines Papiers , alles über die Verwendung von Monaden, um 2D-Raum zu schnitzen. Da Sie nach einer eleganten Lösung gefragt haben, bin ich gezwungen, Ihnen eine Zusammenfassung ihrer Arbeit zu geben, obwohl ich Ihnen nicht empfehlen würde, dies in Ihre Codebase zu integrieren - ich vermute, es ist wahrscheinlich einfacher, nur mit einer Bibliothek wie boxes und die Ausschneide- und Größenänderungslogik manuell mit manueller Fehlerbehandlung ankurbeln.

Ihr erstes Tree ist ein Schritt in die richtige Richtung. Wir können eine Monad -Instanz schreiben, um Bäume miteinander zu verbinden:

%Vor%

Tree s join nimmt einen Baum mit Bäumen an seinen Blättern und lässt Sie den ganzen Weg laufen auf den Boden, ohne auf halbem Weg anhalten zu müssen. Es kann hilfreich sein, an Tree als zu denken freie Monade , wie @danidiaz in eine Antwort gezeigt hat. Oder ​​könnte Kmett sagen , dass Sie eine sehr einfache Syntax haben, die eine Terminusersetzung erlaubt, deren Var Leaf heißt.

Wie auch immer, der Punkt ist, dass Sie >>= verwenden können, um Bäume zu züchten, indem Sie fortlaufend ihre Blätter zerhacken. Hier habe ich eine eindimensionale Benutzeroberfläche (vergessen wir% code% im Moment) mit einem einzigen Fenster, das ein Direction enthält, und indem ich es wiederholt in zwei Hälften zerschneide, lande ich mit acht kleineren Fenstern.

%Vor%

(Im wirklichen Leben würden Sie wahrscheinlich nur ein Fenster nach dem anderen aufschneiden, indem Sie seine ID in Ihrer Bindungsfunktion überprüfen und unverändert zurückgeben, wenn es nicht die gesuchte ist.)

Das Problem ist, dass String kein Verständnis dafür hat, dass der physische Raum eine begrenzte und wertvolle Ressource ist. Mit Tree können Sie fmap s durch a s ersetzen, aber die resultierende Struktur passt nicht auf den Bildschirm, wenn b s mehr Platz belegt als b s!

%Vor%

Das wird in zwei Dimensionen ernster, weil Boxen sich gegenseitig herumdrücken und reißen können. Wenn das mittlere Fenster versehentlich verkleinert wird, bekomme ich entweder eine verformte Box oder ein Loch in der Mitte (abhängig vom Aufenthaltsort im Baum).

%Vor%

Das Erweitern eines Fensters ist durchaus sinnvoll, aber in der realen Welt muss der Raum, in den es expandiert, irgendwo herkommen. Sie können kein Fenster vergrößern, ohne ein anderes zu verkleinern, und umgekehrt. Dies ist nicht die Art von Operation, die mit a durchgeführt werden kann, die lokale Substitutionen an einzelnen Blattknoten ausführt; Sie müssen sich die Geschwister eines Fensters ansehen, um zu wissen, wer den angrenzenden Raum belegt.

Sie sollten also >>= nicht verwenden dürfen, um den Inhalt so zu ändern. Lindley und McBride's Idee ist es, dem Typ-Checker beizubringen, wie man Boxen zusammenfügt. Verwenden von natürlichen Zahlen auf Typenebene und Hinzufügen,

%Vor%

Sie arbeiten mit Inhalten, die durch ihre Breite und Höhe indiziert sind. (In der Arbeit verwenden sie 2D-Matrizen, die als Vektoren von Vektoren dargestellt werden, aber aus Effizienzgründen möchten Sie vielleicht ein Array mit einem Phantom-Typ verwenden, der seine Größe misst.)

%Vor%

Wenn Sie zwei Felder nebeneinander mit >>= platzieren, müssen sie dieselbe Höhe haben. Wenn Sie sie mit Hor übereinander platzieren, müssen sie dieselbe Breite haben.

%Vor%

Jetzt können wir eine Monade bauen, um diese Bäume zu verbinden. Die Semantik von Ver hat sich nicht geändert - sie fügt ein 2D-Objekt in einem return selbst hinzu.

%Vor%

Nun denken wir an Box . Im Allgemeinen besteht eine Box aus einer Anzahl von Stücken unterschiedlicher Größe, die irgendwie zusammengesetzt sind, um eine größere Box zu erzeugen. Unten habe ich drei Teile mit den Größen 2x1, 2x2 und 1x3, die eine 3x3-Box ergeben. Diese Box sieht ungefähr wie >>= aus.

%Vor%

Während Sie, der Aufrufer von Content , die äußeren Maße Ihrer Box kennen, kennen Sie nicht die Dimensionen der einzelnen Teile des Inhalts, aus denen sie bestehen. Wie kann man erwarten, dass die Größe des Inhalts erhalten bleibt, wenn Sie ihn mit Hor (Ver (Content 2x1) (Content 2x2)) Content 1x3 schneiden? Sie müssen eine Funktion schreiben, die die Größe ohne a priori Wissen über die Größe behält.

Also >>= nimmt ein >>= einer bekannten Größe >>= , zerlegt es, um den Inhalt zu finden, verarbeitet es mit einer Funktion, die die (unbekannte) Größe des Inhalts beibehält, den Sie ihm geben * und setzt es wieder zusammen, um eine neue Box mit der gleichen Größe Box zu produzieren. Beachten Sie den Rang-2-Typ, der die Tatsache widerspiegelt, dass der Aufrufer von wh keine Kontrolle über die Dimensionen des Inhalts hat, mit dem die Fortsetzung aufgerufen wird.

%Vor%

Wenn Sie ein Typ-Synonym wh für indexerhaltende Funktionen verwenden und die Argumente umdrehen, erhalten Sie etwas, das für >>= s nur wie ~> aussieht, aber mit einer anderen Art von Pfeil. Kleisli Komposition kommt auch ziemlich hübsch aus.

%Vor%

Also das sind Monaden über indizierte Sets. (Mehr in Kleisli Arrows of Outrageous Fortune .) das Papier sie bauen ein Bündel mehr Infrastruktur, um das Zuschneiden und Neuanordnen von Boxen zu unterstützen wäre wahrscheinlich nützlich für den Aufbau einer Benutzeroberfläche. Aus Effizienzgründen können Sie das aktuell fokussierte Fenster auch mit einem Reißverschluss , was eine lustige Übung ist. Übrigens denke ich, dass Hasochismus eine großartige Einführung in die Phantasiearten im Allgemeinen ist, nicht nur als Lösung für dieses spezielle Problem.

* Angenommen, der =<< Index ist in der Tat ein genaues Maß für seine physikalische Größe

    
Benjamin Hodgson 20.03.2017, 06:22
quelle
1

Ich würde Ihren Typ als Monade darstellen und >>= für split verwenden.

%Vor%

Wie bei close würde ich wahrscheinlich cata oder para von recursion-schemes , da close scheinbar von unten nach oben funktioniert und höchstens Wissen über Knoteneltern und Geschwister benötigt. Sie können sich auch an Control.Lens.Plated wenden .

Übrigens hat Free bereits eine Recursive Instanz. FreeF TreeF a wäre die entsprechende Algebra. Aber du hast erwähnt, dass es nicht gut gelaufen ist.

Die direkte Arbeit mit den Bausteinen Free und FreeT könnte sich als umständlich erweisen. Vielleicht könnten ein paar Synonymen helfen.

    
danidiaz 19.03.2017 21:47
quelle

Tags und Links