Ich habe einen Datentyp wie folgt mit einer entsprechenden %code% -Instanz gesehen:
%Vor%Sie können den vollständigen Code in einem Gist auf Github finden.
So kann %code% verwendet werden:
%Vor%%code% endet als ein Baum, der folgendermaßen aussieht:
%Vor%%code% kann verwendet werden, um Sequenzen von %code% Operationen ( %code% und %code% ) in eine AST umzuwandeln. Dieser AST kann dann in andere %code% interpretiert werden.
Hier ist zum Beispiel eine Übersetzung von %code% in eine %code% , die sicherstellt, dass die angehängte Zeichenfolge optimal geschieht:
%Vor%Das ist wirklich nett. Es ist bequem, dass wir sicher sein können, dass %code% anhängen in der richtigen Reihenfolge geschieht. Wir müssen uns nicht um das links-assoziierte %code% s kümmern.
Was mich jedoch beunruhigt, ist, dass die %code% -Instanz für %code% keine legale %code% -Instanz ist.
Nehmen Sie zum Beispiel das erste %code% Gesetz:
%Vor%Wenn wir %code% be %code% zulassen, erhalten wir Folgendes:
%Vor%Sie können sehen, dass %code% nicht gleich %code% ist. Die anderen %code% -Gesetze gelten auch aus ähnlichen Gründen nicht.
Hascellers sind in der Regel gegen unrechtmäßige Instanzen. Aber ich denke, das ist ein besonderer Fall. Wir versuchen nur, eine Struktur aufzubauen, die in ein anderes %code% interpretiert werden kann. Im Fall von %code% können wir sicherstellen, dass die %code% -Gesetze für %code% in der Funktion %code% gelten.
Ist es jemals in Ordnung, diese Arten von nicht-rechtmäßigen Instanzen zu verwenden, um einen AST aufzubauen?
Dies ist eine Frage der Meinung. (Ich bin fest im 'Niemals' Camp.)
Gibt es bestimmte Probleme, auf die bei der Verwendung dieser Arten von nicht-rechtmäßigen Instanzen geachtet werden muss?
Bearbeiten, um Fragen in Kommentaren zu beantworten:
Wären Sie in der Lage, konkrete Beispiele dafür zu finden, wie sie die kognitive Belastung der Benutzer erhöhen?
Stellen Sie sich vor, wie verärgert Sie wären, wenn jemand das in C machen würde:
%Vor%Jetzt müssen wir den Umfang dieser Pseudo-While-Definition und ihrer Implikationen verfolgen. Es ist ein Nicht-Haskell-Beispiel, aber ich denke, das Prinzip ist das gleiche. Wir sollten nicht erwarten, dass die Semantik von %code% in einer bestimmten Quelldatei anders ist, genauso wie wir nicht erwarten sollten, dass die Semantik von %code% für einen bestimmten Datentyp unterschiedlich ist.
Wenn wir sagen, dass etwas ein X ist, dann sollte es ein X sein, weil Leute die Semantik von X verstehen. Das Prinzip hier ist erzeugt keine Ausnahmen zu gut verstandenen Konzepten .
Ich denke, dass der Gebrauch von rechtmäßigen Abstraktionen (wie Monoid) in erster Linie dazu dient, die Notwendigkeit zu verringern, dass Programmierer eine Vielzahl unterschiedlicher Semantiken lernen und sich daran erinnern müssen. Jede von uns geschaffene Ausnahme untergräbt dieses Ziel. In der Tat macht es es noch schlimmer; wir müssen uns an die Abstraktion erinnern und darüber hinaus alle Ausnahmen beachten. (Nebenbei, ich bewundere aber diejenigen, die Englisch als Zweitsprache gelernt haben.)
Oder wie kann es zu möglichen Fehlern führen?
einige Bibliothek:
%Vor%mein Code:
%Vor%ein anderer Programmierer (oder ich in ein paar Monaten):
%Vor%Gibt es eine alternative Möglichkeit, Code zu schreiben, der so etwas wie Foo verwendet?
Ich würde wahrscheinlich nur den Konstruktor verwenden, um zu konstruieren:
%Vor%oder machen Sie einen Operator:
%Vor%Dies wird für meisten nicht-trivialen Datenstrukturen angezeigt. Die einzigen Ausnahmen, die mir von oben in den Sinn kommen, sind (einige) Trie-ähnliche Strukturen.
Ausbalancierte Baumdatenstrukturen erlauben mehrere Ausbalancierungen der meisten Werte. Dies gilt für AVL-Bäume, rot-schwarze Bäume, B-Bäume, 2-3 Finger-Bäume, etc.
Datenstrukturen, die um das "Umbauen" herum entworfen wurden, wie Hood-Melville-Warteschlangen, ermöglichen variable Mengen an Duplizierung innerhalb von Strukturen, die die meisten Werte repräsentieren.
Datenstrukturen, die effiziente Prioritätswarteschlangen implementieren, ermöglichen Mehrfachanordnungen von Elementen.
Hash-Tabellen ordnen Elemente unterschiedlich an, je nachdem, wann Kollisionen auftreten.
Keine dieser Strukturen kann ohne diese Flexibilität asymptotisch so effizient sein. Die Flexibilität bricht jedoch immer Gesetze unter strengster Interpretation. In Haskell ist der einzige gute Weg, dies zu bewältigen, indem man das Modulsystem verwendet, um sicherzustellen, dass niemand das Problem erkennen kann. In experimentell abhängigen Sprachen haben Forscher an Dingen wie der Beobachtungstyptheorie und der Homotopietypentheorie gearbeitet, um bessere Wege zu finden, über "Gleichheit" zu sprechen, aber diese Forschung ist weit davon entfernt, praktisch zu werden.
Zitieren diese Antwort zu einer ähnlichen Frage :
Man kann sich das von diesem alternativen Standpunkt aus vorstellen: das Gesetz (a & lt; & gt; b) & lt; & gt; c = a & lt; & gt; (b & lt; & gt; c) spezifiziert nicht, welche Gleichheit verwendet werden sollte, d. h. welche spezifische Beziehung das = bezeichnet. Es ist naheliegend, es im Hinblick auf die strukturelle Gleichheit zu betrachten, aber beachten Sie, dass sehr wenige Typklassengesetze tatsächlich der strukturellen Gleichheit gerecht werden (zB versuchen, fmap id = id für [] im Gegensatz zu forall x zu beweisen. Fmap id x = id x) .
Zum Beispiel ist es meistens in Ordnung, wenn Sie die Konstruktoren von %code% nicht exportieren und nur Funktionen exportieren, die sich aus Sicht der Benutzer so verhalten, als wäre %code% ein Monoid. Aber die meiste Zeit ist es möglich, eine Repräsentation zu finden, die strukturell ein Monoid ist, in der Praxis gut genug, aber vielleicht nicht so allgemein (unten kann man sich nicht beliebig nach der Tatsache neu zuordnen, weil Interpretation mit Konstruktion vermischt ist). p> %Vor%
( %code% )
Ich habe einen Datentyp wie folgt mit einer entsprechenden Monoid
-Instanz gesehen:
Sie können den vollständigen Code in einem Gist auf Github finden.
So kann Foo
verwendet werden:
exampleFoo
endet als ein Baum, der folgendermaßen aussieht:
Foo
kann verwendet werden, um Sequenzen von Monoid
Operationen ( mempty
und mappend
) in eine AST umzuwandeln. Dieser AST kann dann in andere Monoid
interpretiert werden.
Hier ist zum Beispiel eine Übersetzung von Foo
in eine String
, die sicherstellt, dass die angehängte Zeichenfolge optimal geschieht:
Das ist wirklich nett. Es ist bequem, dass wir sicher sein können, dass String
anhängen in der richtigen Reihenfolge geschieht. Wir müssen uns nicht um das links-assoziierte mappend
s kümmern.
Was mich jedoch beunruhigt, ist, dass die Monoid
-Instanz für Foo
keine legale Monoid
-Instanz ist.
Nehmen Sie zum Beispiel das erste Monoid
Gesetz:
Wenn wir x
be FooEmpty "hello"
zulassen, erhalten wir Folgendes:
Sie können sehen, dass FooAppend (FooEmpty "") (FooEmpty "hello")
nicht gleich FooEmpty "hello"
ist. Die anderen Monoid
-Gesetze gelten auch aus ähnlichen Gründen nicht.
Hascellers sind in der Regel gegen unrechtmäßige Instanzen. Aber ich denke, das ist ein besonderer Fall. Wir versuchen nur, eine Struktur aufzubauen, die in ein anderes Monoid
interpretiert werden kann. Im Fall von Foo
können wir sicherstellen, dass die Monoid
-Gesetze für String
in der Funktion fooInterp
gelten.
Foo
verwendet? Eine Möglichkeit, die Interpretation einer monoidalen Struktur zu ermöglichen, anstatt mappend
direkt auf einem Typ zu verwenden? Zitieren diese Antwort zu einer ähnlichen Frage :
Man kann sich das von diesem alternativen Standpunkt aus vorstellen: das Gesetz (a & lt; & gt; b) & lt; & gt; c = a & lt; & gt; (b & lt; & gt; c) spezifiziert nicht, welche Gleichheit verwendet werden sollte, d. h. welche spezifische Beziehung das = bezeichnet. Es ist naheliegend, es im Hinblick auf die strukturelle Gleichheit zu betrachten, aber beachten Sie, dass sehr wenige Typklassengesetze tatsächlich der strukturellen Gleichheit gerecht werden (zB versuchen, fmap id = id für [] im Gegensatz zu forall x zu beweisen. Fmap id x = id x) .
Zum Beispiel ist es meistens in Ordnung, wenn Sie die Konstruktoren von Foo
nicht exportieren und nur Funktionen exportieren, die sich aus Sicht der Benutzer so verhalten, als wäre Foo
ein Monoid. Aber die meiste Zeit ist es möglich, eine Repräsentation zu finden, die strukturell ein Monoid ist, in der Praxis gut genug, aber vielleicht nicht so allgemein (unten kann man sich nicht beliebig nach der Tatsache neu zuordnen, weil Interpretation mit Konstruktion vermischt ist). p>
%Vor%
( Data.Monoid.Endo
)
Hier ist eine weitere SO-Frage, wo eine nicht-strukturelle Struktur ( Alternative
) ) wird zuerst betrachtet.
Dies wird für meisten nicht-trivialen Datenstrukturen angezeigt. Die einzigen Ausnahmen, die mir von oben in den Sinn kommen, sind (einige) Trie-ähnliche Strukturen.
Ausbalancierte Baumdatenstrukturen erlauben mehrere Ausbalancierungen der meisten Werte. Dies gilt für AVL-Bäume, rot-schwarze Bäume, B-Bäume, 2-3 Finger-Bäume, etc.
Datenstrukturen, die um das "Umbauen" herum entworfen wurden, wie Hood-Melville-Warteschlangen, ermöglichen variable Mengen an Duplizierung innerhalb von Strukturen, die die meisten Werte repräsentieren.
Datenstrukturen, die effiziente Prioritätswarteschlangen implementieren, ermöglichen Mehrfachanordnungen von Elementen.
Hash-Tabellen ordnen Elemente unterschiedlich an, je nachdem, wann Kollisionen auftreten.
Keine dieser Strukturen kann ohne diese Flexibilität asymptotisch so effizient sein. Die Flexibilität bricht jedoch immer Gesetze unter strengster Interpretation. In Haskell ist der einzige gute Weg, dies zu bewältigen, indem man das Modulsystem verwendet, um sicherzustellen, dass niemand das Problem erkennen kann. In experimentell abhängigen Sprachen haben Forscher an Dingen wie der Beobachtungstyptheorie und der Homotopietypentheorie gearbeitet, um bessere Wege zu finden, über "Gleichheit" zu sprechen, aber diese Forschung ist weit davon entfernt, praktisch zu werden.
Ist es jemals in Ordnung, diese Arten von nicht-rechtmäßigen Instanzen zu verwenden, um einen AST aufzubauen?
Dies ist eine Frage der Meinung. (Ich bin fest im 'Niemals' Camp.)
Gibt es bestimmte Probleme, auf die bei der Verwendung dieser Arten von nicht-rechtmäßigen Instanzen geachtet werden muss?
Bearbeiten, um Fragen in Kommentaren zu beantworten:
Wären Sie in der Lage, konkrete Beispiele dafür zu finden, wie sie die kognitive Belastung der Benutzer erhöhen?
Stellen Sie sich vor, wie verärgert Sie wären, wenn jemand das in C machen würde:
%Vor% Jetzt müssen wir den Umfang dieser Pseudo-While-Definition und ihrer Implikationen verfolgen. Es ist ein Nicht-Haskell-Beispiel, aber ich denke, das Prinzip ist das gleiche. Wir sollten nicht erwarten, dass die Semantik von while
in einer bestimmten Quelldatei anders ist, genauso wie wir nicht erwarten sollten, dass die Semantik von Monoid
für einen bestimmten Datentyp unterschiedlich ist.
Wenn wir sagen, dass etwas ein X ist, dann sollte es ein X sein, weil Leute die Semantik von X verstehen. Das Prinzip hier ist erzeugt keine Ausnahmen zu gut verstandenen Konzepten .
Ich denke, dass der Gebrauch von rechtmäßigen Abstraktionen (wie Monoid) in erster Linie dazu dient, die Notwendigkeit zu verringern, dass Programmierer eine Vielzahl unterschiedlicher Semantiken lernen und sich daran erinnern müssen. Jede von uns geschaffene Ausnahme untergräbt dieses Ziel. In der Tat macht es es noch schlimmer; wir müssen uns an die Abstraktion erinnern und darüber hinaus alle Ausnahmen beachten. (Nebenbei, ich bewundere aber diejenigen, die Englisch als Zweitsprache gelernt haben.)
Oder wie kann es zu möglichen Fehlern führen?
einige Bibliothek:
%Vor%mein Code:
%Vor%ein anderer Programmierer (oder ich in ein paar Monaten):
%Vor%Gibt es eine alternative Möglichkeit, Code zu schreiben, der so etwas wie Foo verwendet?
Ich würde wahrscheinlich nur den Konstruktor verwenden, um zu konstruieren:
%Vor%oder machen Sie einen Operator:
%Vor%Tags und Links haskell monoids abstract-syntax-tree typeclass interpreter