Es ist einfach genug, einen Baum oder eine Liste in Haskell mithilfe algebraischer Datentypen darzustellen. Aber wie würden Sie vorgehen, um eine Grafik typografisch darzustellen? Es scheint, dass Sie Zeiger haben müssen. Ich vermute, Sie könnten so etwas haben
type Nodetag = String
type Neighbours = [Nodetag]
data Node a = Node a Nodetag Neighbours
Und das wäre machbar. Es fühlt sich jedoch etwas entkoppelt an; Die Verknüpfungen zwischen verschiedenen Knoten in der Struktur "fühlen" sich nicht so solide an wie die Verknüpfungen zwischen den aktuellen vorherigen und nächsten Elementen in einer Liste oder den Eltern und untergeordneten Elementen eines Knotens in einem Baum. Ich habe die Vermutung, dass algebraische Manipulationen am Diagramm, wie ich es definiert habe, durch die durch das Tag-System eingeführte Indirektionsebene etwas behindert würden.
Vor allem dieses Gefühl des Zweifels und die Wahrnehmung der Uneleganz veranlassen mich, diese Frage zu stellen. Gibt es eine bessere / mathematisch elegantere Möglichkeit, Diagramme in Haskell zu definieren? Oder bin ich auf etwas gestoßen, das von Natur aus hart / grundlegend ist? Rekursive Datenstrukturen sind süß, aber dies scheint etwas anderes zu sein. Eine selbstreferenzielle Datenstruktur in einem anderen Sinne als Bäume und Listen selbstreferenziell. Es ist so, als ob Listen und Bäume auf Typebene selbstreferenziell sind, aber Diagramme sind auf Wertebene selbstreferenziell.
Also, was ist wirklich los?
quelle
fgl
Paket entwickelte sich daraus.Antworten:
Ich finde es auch umständlich, Datenstrukturen mit Zyklen in einer reinen Sprache darzustellen. Es sind die Zyklen, die wirklich das Problem sind; Da Werte gemeinsam genutzt werden können, ist jedes ADT, das ein Mitglied des Typs (einschließlich Listen und Bäume) enthalten kann, tatsächlich eine DAG (Directed Acyclic Graph). Das grundlegende Problem ist, dass, wenn Sie Werte A und B haben, wobei A B und B A enthält, keiner von beiden erstellt werden kann, bevor der andere existiert. Weil Haskell faul ist, können Sie einen Trick verwenden, der als Binden des Knotens bekannt ist , um dies zu umgehen , aber das macht mein Gehirn weh (weil ich noch nicht viel davon getan habe). Ich habe bisher mehr von meiner umfangreichen Programmierung in Mercury als in Haskell gemacht, und Mercury ist streng, so dass das Binden von Knoten nicht hilft.
Wenn ich zuvor darauf gestoßen bin, habe ich normalerweise auf zusätzliche Indirektion zurückgegriffen, wie Sie vorschlagen. häufig durch Verwendung einer Zuordnung von IDs zu den tatsächlichen Elementen und durch Elemente, die Verweise auf die IDs anstelle auf andere Elemente enthalten. Die Hauptsache, die mir dabei nicht gefallen hat (abgesehen von der offensichtlichen Ineffizienz), ist, dass es sich fragiler anfühlte und die möglichen Fehler beim Nachschlagen einer nicht vorhandenen ID oder beim Versuch, dieselbe ID mehreren zuzuweisen, einführte Element. Sie können Code schreiben , so dass diese Fehler nicht auftreten, natürlich, und sogar verstecken sie hinter Abstraktionen , so dass die einzigen Orte , wo solche Fehler könnten sind begrenzt auftreten. Aber es ist noch eine Sache, etwas falsch zu machen.
Eine schnelle Google- Suche nach "Haskell-Grafik" führte mich jedoch zu http://www.haskell.org/haskellwiki/The_Monad.Reader/Issue5/Practical_Graph_Handling , was nach einer lohnenden Lektüre aussieht.
quelle
In Shang's Antwort können Sie sehen, wie man ein Diagramm mit Faulheit darstellt. Das Problem bei diesen Darstellungen ist, dass sie sehr schwer zu ändern sind. Der Trick zum Verknüpfen von Knoten ist nur dann nützlich, wenn Sie ein Diagramm einmal erstellen möchten und es sich danach nie mehr ändert.
In der Praxis verwende ich die Fußgängerdarstellungen , wenn ich tatsächlich etwas mit meinem Diagramm tun möchte :
Wenn Sie das Diagramm häufig ändern oder bearbeiten möchten, empfehle ich die Verwendung einer Darstellung basierend auf Huets Reißverschluss. Dies ist die Darstellung, die intern in GHC für Kontrollflussdiagramme verwendet wird. Sie können hier darüber lesen:
Ein anwendbares Kontrollflussdiagramm basierend auf Huets Reißverschluss
Hoopl: Eine modulare, wiederverwendbare Bibliothek für die Analyse und Transformation von Datenflüssen
quelle
Wie Ben erwähnte, werden zyklische Daten in Haskell durch einen Mechanismus konstruiert, der "Binden des Knotens" genannt wird. In der Praxis bedeutet dies, dass wir gegenseitig rekursive Deklarationen mit
let
oderwhere
Klauseln schreiben , was funktioniert, weil die gegenseitig rekursiven Teile träge ausgewertet werden.Hier ist ein Beispiel für einen Diagrammtyp:
Wie Sie sehen können, verwenden wir tatsächliche
Node
Referenzen anstelle von Indirektion. Hier erfahren Sie, wie Sie eine Funktion implementieren, die das Diagramm aus einer Liste von Beschriftungszuordnungen erstellt.Wir nehmen eine Liste von
(nodeLabel, [adjacentLabel])
Paaren auf und konstruieren die tatsächlichenNode
Werte über eine Zwischen-Lookup-Liste (die das eigentliche Knotenbinden durchführt). Der Trick ist, dassnodeLookupList
(der den Typ hat[(a, Node a)]
) mit konstruiert wirdmkNode
, was sich wiederum auf das beziehtnodeLookupList
, um die benachbarten Knoten zu finden.quelle
Es ist wahr, Graphen sind nicht algebraisch. Um dieses Problem zu lösen, haben Sie mehrere Möglichkeiten:
Int
s) geben und indirekt und nicht algebraisch auf sie verweisen. Dies kann erheblich komfortabler gestaltet werden, indem der Typ abstrakt gemacht und eine Schnittstelle bereitgestellt wird, die die Indirektion für Sie in Einklang bringt. Dies ist der Ansatz von z. B. fgl und anderen praktischen Grafikbibliotheken zu Hackage.Jede der oben genannten Optionen hat Vor- und Nachteile. Wählen Sie die für Sie am besten geeignete aus.
quelle
Einige andere haben kurz
fgl
Martin Erwigs induktive Graphen und funktionale Graphenalgorithmen erwähnt , aber es lohnt sich wahrscheinlich, eine Antwort zu schreiben, die tatsächlich einen Eindruck von den Datentypen hinter dem Ansatz der induktiven Darstellung vermittelt.Erwig stellt in seiner Arbeit folgende Typen vor:
(Die Darstellung in
fgl
ist etwas anders und verwendet Typklassen - aber die Idee ist im Wesentlichen dieselbe.)Erwig beschreibt einen Multigraph, in dem Knoten und Kanten Beschriftungen haben und in dem alle Kanten gerichtet sind. A
Node
hat eine Art Etiketta
; Eine Kante hat eine Art Etikettb
. AContext
ist einfach (1) eine Liste von markierten Kanten, die auf einen bestimmten Knoten zeigen, (2) der betreffende Knoten, (3) die Beschriftung des Knotens und (4) die Liste von markierten Kanten, die vom Knoten zeigen. AGraph
kann dann induktiv entweder alsEmpty
oder alsContext
Verschmelzung (mit&
) mit einem Bestehenden verstanden werdenGraph
.Wie Erwig Notizen können wir erzeugen nicht frei eine
Graph
mitEmpty
und&
, wie wir eine Liste mit der generieren könnteCons
undNil
Konstrukteuren oder eineTree
mitLeaf
undBranch
. Im Gegensatz zu Listen (wie andere bereits erwähnt haben) wird es auch keine kanonische Darstellung von a gebenGraph
. Dies sind entscheidende Unterschiede.Was diese Darstellung jedoch so leistungsfähig und den typischen Haskell-Darstellungen von Listen und Bäumen so ähnlich macht, ist, dass der
Graph
Datentyp hier induktiv definiert wird . Die Tatsache, dass eine Liste induktiv definiert ist, ermöglicht es uns, die Musterübereinstimmung so prägnant zu gestalten, ein einzelnes Element zu verarbeiten und den Rest der Liste rekursiv zu verarbeiten. Ebenso ermöglicht es Erwigs induktive Darstellung, einen Graphen einzeln rekursiv zu verarbeitenContext
. Diese Darstellung eines Diagramms eignet sich für eine einfache Definition einer Methode zum Abbilden eines Diagramms (gmap
) sowie zum Durchführen ungeordneter Falten über Diagramme (ufold
).Die anderen Kommentare auf dieser Seite sind großartig. Der Hauptgrund, warum ich diese Antwort geschrieben habe, ist jedoch, dass ich beim Lesen von Sätzen wie "Diagramme sind nicht algebraisch" befürchte, dass einige Leser unweigerlich den (falschen) Eindruck erwecken, dass niemand einen guten Weg gefunden hat, Diagramme darzustellen in Haskell auf eine Weise, die es ermöglicht, Muster auf ihnen abzugleichen, sie zuzuordnen, sie zu falten oder im Allgemeinen die coolen, funktionalen Dinge zu tun, die wir von Listen und Bäumen gewohnt sind.
quelle
Ich mochte immer Martin Erwigs Ansatz in "Induktive Graphen und funktionale Graphenalgorithmen", den Sie hier lesen können . FWIW, ich habe auch einmal eine Scala-Implementierung geschrieben, siehe https://github.com/nicolast/scalagraphs .
quelle
Jede Diskussion über die Darstellung von Graphen in Haskell erfordert die Erwähnung der Data-Reify-Bibliothek von Andy Gill (hier ist das Papier ).
Mit der Darstellung im "Tying-the-Knot" -Stil können sehr elegante DSLs erstellt werden (siehe Beispiel unten). Die Datenstruktur ist jedoch von begrenztem Nutzen. Gill's Bibliothek bietet Ihnen das Beste aus beiden Welten. Sie können eine DSL-Funktion zum Verknüpfen des Knotens verwenden, dann aber das zeigerbasierte Diagramm in ein beschriftungsbasiertes Diagramm konvertieren, damit Sie die Algorithmen Ihrer Wahl darauf ausführen können.
Hier ist ein einfaches Beispiel:
Um den obigen Code auszuführen, benötigen Sie die folgenden Definitionen:
Ich möchte betonen, dass dies ein vereinfachtes DSL ist, aber der Himmel ist die Grenze! Ich habe ein sehr funktionsfähiges DSL entworfen, einschließlich einer schönen baumartigen Syntax, mit der ein Knoten einen Anfangswert an einige seiner untergeordneten Knoten sendet, und vielen praktischen Funktionen zum Erstellen bestimmter Knotentypen. Natürlich waren der Node-Datentyp und die mapDeRef-Definitionen viel komplizierter.
quelle
Ich mag diese Implementierung eines Diagramms von hier
quelle