Frage Verwaiste Instanzen in Haskell


Beim kompilieren meiner Haskell Applikation mit dem -Wall Option beschwert sich GHC über verwaiste Instanzen, zum Beispiel:

Publisher.hs:45:9:
    Warning: orphan instance: instance ToSElem Result

Die Typklasse ToSElem ist nicht meins, es ist definiert durch HStringTemplate.

Jetzt weiß ich, wie ich das beheben kann (verschiebe die Instanzdeklaration in das Modul, in dem Result deklariert ist), und ich weiß es warum würde GHC verwaiste Instanzen lieber vermeiden?, aber ich glaube immer noch, dass mein Weg besser ist. Es ist mir egal, ob der Compiler Unannehmlichkeiten hat - eher als ich.

Der Grund, warum ich meine ToSElem Instanzen in dem Modul Publisher ist, da es das Modul Publisher ist, das von HStringTemplate und nicht den anderen Modulen abhängt. Ich versuche, eine Trennung der Bedenken aufrechtzuerhalten und zu vermeiden, dass jedes Modul von HStringTemplate abhängt.

Ich dachte, dass einer der Vorteile von Haskells Typklassen im Vergleich zu den Java-Schnittstellen darin besteht, dass sie offen statt geschlossen sind und die Instanzen daher nicht an derselben Stelle wie der Datentyp deklariert werden müssen. GHCs Ratschlag scheint zu sein, dies zu ignorieren.

Also, was ich suche, ist entweder eine Bestätigung dafür, dass mein Denken vernünftig ist und dass ich berechtigt wäre, diese Warnung zu ignorieren / zu unterdrücken, oder ein überzeugenderes Argument dagegen, Dinge auf meine Art zu tun.


75
2018-06-20 14:17


Ursprung


Antworten:


Ich verstehe, warum Sie das tun wollen, aber leider ist es vielleicht nur eine Illusion, dass Haskell-Klassen in der Art, wie Sie sagen, "offen" zu sein scheinen. Viele Leute glauben, dass die Möglichkeit, dies zu tun, ein Fehler in der Haskell-Spezifikation ist, aus Gründen, die ich unten erläutern werde. Wenn es wirklich nicht angemessen ist, dass die Instanz entweder in dem Modul, in dem die Klasse deklariert ist, oder in dem Modul, in dem der Typ deklariert ist, deklariert werden muss, ist das wahrscheinlich ein Zeichen, das Sie verwenden sollten newtype oder irgendeinen anderen Wrapper um deinen Typ.

Die Gründe, warum verwaiste Instanzen vermieden werden müssen, reichen weit tiefer als die Bequemlichkeit des Compilers. Dieses Thema ist eher umstritten, wie Sie anderen Antworten entnehmen können. Um die Diskussion auszugleichen, werde ich den Standpunkt erläutern, dass man niemals Waisenfälle schreiben sollte, was meiner Meinung nach die Mehrheitsmeinung unter den erfahrenen Haskellern ist. Meine eigene Meinung ist irgendwo in der Mitte, was ich am Ende erklären werde.

Das Problem rührt von der Tatsache her, dass, wenn mehr als eine Instanzdeklaration für dieselbe Klasse und denselben Typ existiert, es im Standard-Haskell keinen Mechanismus gibt, um anzugeben, welcher zu verwenden ist. Vielmehr wird das Programm vom Compiler abgelehnt.

Der einfachste Effekt davon ist, dass Sie ein perfekt funktionierendes Programm haben könnten, das plötzlich aufhört zu kompilieren, aufgrund einer Änderung, die jemand anderes in einer weit entfernten Abhängigkeit von Ihrem Modul macht.

Schlimmer noch, es ist möglich, dass ein Arbeitsprogramm startet Absturz zur Laufzeit wegen einer entfernten Veränderung. Sie könnten eine Methode verwenden, von der Sie annehmen, dass sie von einer bestimmten Instanzdeklaration stammt, und sie könnte im Hintergrund durch eine andere Instanz ersetzt werden, die gerade anders ist, um zu verursachen, dass Ihr Programm unerklärlicherweise abstürzt.

Personen, die garantieren wollen, dass ihnen diese Probleme niemals passieren werden, müssen der Regel folgen, dass, wenn irgendjemand irgendwo eine Instanz einer bestimmten Klasse für einen bestimmten Typ deklariert hat, keine andere Instanz jemals wieder in einem geschriebenen Programm deklariert werden muss von jemandem. Natürlich gibt es die Umgehung der Verwendung eines newtype um eine neue Instanz zu deklarieren, aber das ist immer zumindest eine kleine Unannehmlichkeit, und manchmal eine große. In diesem Sinne sind diejenigen, die absichtlich Waisenfälle schreiben, ziemlich unhöflich.

Was sollte also für dieses Problem getan werden? Das Anti-Orphan-Instanz-Camp sagt, dass die GHC-Warnung ein Fehler ist. Es muss ein Fehler sein, der jeden Versuch zurückweist, eine verwaiste Instanz zu deklarieren. In der Zwischenzeit müssen wir Selbstdisziplin üben und sie um jeden Preis vermeiden.

Wie Sie gesehen haben, gibt es diejenigen, die sich nicht so große Sorgen um diese potenziellen Probleme machen. Sie ermutigen tatsächlich die Verwendung von Orphan-Instanzen als Werkzeug zur Trennung von Anliegen, wie Sie vorschlagen, und sagen, dass man nur von Fall zu Fall sicherstellen sollte, dass es kein Problem gibt. Ich wurde oft genug von Waisenfällen anderer Leute belästigt, um überzeugt zu sein, dass diese Haltung zu freizügig ist.

Ich denke, die richtige Lösung wäre, dem Importmechanismus von Haskell eine Erweiterung hinzuzufügen, die den Import von Instanzen steuert. Das würde die Probleme nicht vollständig lösen, aber es würde helfen, unsere Programme vor Schäden durch die Waisenfälle zu schützen, die es auf der Welt bereits gibt. Und dann, mit der Zeit, könnte ich zu der Überzeugung gelangen, dass in bestimmten begrenzten Fällen eine Waiseninstanz vielleicht nicht so schlecht ist. (Und genau diese Versuchung ist der Grund, dass einige im Anti-Waisen-Instanz-Lager gegen meinen Vorschlag sind.)

Meine Schlussfolgerung aus all dem ist, dass ich zumindest vorläufig rate, dass Sie es vermeiden, irgendwelche Orphan-Instanzen zu deklarieren, aus keinem anderen Grund Rücksicht auf andere zu nehmen. Benutze einen newtype.


81
2018-06-20 15:22



Geh und unterdrücke diese Warnung!

Sie sind in guter Gesellschaft. Conal macht das in "TypeCompose". "chp-mtl" und "chp-transformers" tun es, "control-monad-exception-mtl" und "control-monad-exception-monadsfd" tun es, usw.

Übrigens kennst du das wahrscheinlich schon, aber für diejenigen, die das nicht tun und deine Frage bei einer Suche stolpern:

{-# OPTIONS_GHC -fno-warn-orphans #-}

Bearbeiten:

Ich erkenne die Probleme an, die Yitz in seiner Antwort als echte Probleme erwähnte. Ich sehe jedoch auch keine verwaisten Instanzen als Problem, und ich versuche, das "kleinste aller Übel" zu wählen, was bedeutet, vorsichtigerweise verwaiste Instanzen zu verwenden.

Ich habe nur ein Ausrufezeichen in meiner kurzen Antwort verwendet, weil Ihre Frage zeigt, dass Sie sich der Probleme bereits bewusst sind. Sonst wäre ich weniger begeistert :)

Ein bisschen Abwechslung, aber was ich glaube ist die perfekte Lösung in einer perfekten Welt ohne Kompromisse:

Ich glaube, dass die Probleme, die Yitz erwähnt (ohne zu wissen, welche Instanz ausgewählt wird), in einem "ganzheitlichen" Programmiersystem gelöst werden könnten, wo:

  • Sie bearbeiten nicht nur Textdateien primitiv, sondern werden vielmehr von der Umgebung unterstützt (zum Beispiel werden bei der Codevervollständigung nur Dinge relevanter Typen vorgeschlagen)
  • Die Sprache der "unteren Ebene" hat keine spezielle Unterstützung für Typklassen und stattdessen werden Funktionstabellen explizit weitergegeben
  • Aber die Programmierumgebung "höherer Ebene" zeigt den Code ähnlich wie Haskell jetzt dargestellt wird (normalerweise werden die Funktionstabellen nicht weitergegeben) und wählt die expliziten Typklassen für Sie aus, wenn sie offensichtlich sind (z Beispiel alle Fälle von Functor haben nur eine Auswahl) und wenn es mehrere Beispiele gibt (Zipping-Liste Applicative oder list-monad Applicativ, First / Last / lift vielleicht Monoid), können Sie wählen, welche Instanz verwendet werden soll.
  • In jedem Fall, selbst wenn die Instanz automatisch für Sie ausgewählt wurde, können Sie leicht in der Umgebung sehen, welche Instanz verwendet wurde, mit einer einfachen Schnittstelle (Hyperlink oder Hover-Schnittstelle oder so)

Zurück aus der Fantasy-Welt (oder hoffentlich aus der Zukunft), gerade jetzt: Ich empfehle, verwaiste Instanzen zu vermeiden, während Sie sie immer noch verwenden, wenn Sie "wirklich" brauchen


38
2018-06-20 14:31



Orphan-Instanzen sind ein Ärgernis, aber meiner Meinung nach sind sie manchmal notwendig. Ich kombiniere oft Bibliotheken, wo ein Typ aus einer Bibliothek kommt und eine Klasse aus einer anderen Bibliothek kommt. Natürlich können die Autoren dieser Bibliotheken nicht erwarten, Instanzen für jede denkbare Kombination von Typen und Klassen zur Verfügung zu stellen. Also muss ich sie zur Verfügung stellen und sie sind Waisen.

Die Idee, dass Sie den Typ in einen neuen Typ einfügen müssen, wenn Sie eine Instanz bereitstellen müssen, ist eine Idee mit theoretischem Wert, aber in vielen Fällen ist sie einfach zu langwierig. es ist die Art von Idee, die von Leuten gemacht wird, die nicht Haskell Code schreiben, um ihren Lebensunterhalt zu verdienen. :)

Gehen Sie also voran und stellen Sie verwaiste Instanzen bereit. Sie sind harmlos.
Wenn Sie ghc mit verwaisten Instanzen abstürzen können, dann ist das ein Fehler und sollte als solcher gemeldet werden. (Der Fehler, den ghc hatte, wenn mehrere Instanzen nicht erkannt wurden, ist nicht so schwer zu beheben.)

Aber sei dir bewusst, dass jemand anderes in der Zukunft die Instanz hinzufügen könnte, die du bereits hast, und du möglicherweise einen (Kompilierzeit) -Fehler bekommst.


32
2018-06-21 01:53



In diesem Fall denke ich, dass die Verwendung von verwaisten Instanzen in Ordnung ist. Die allgemeine Faustregel für mich ist - Sie können eine Instanz definieren, wenn Sie die Typklasse "besitzen" oder wenn Sie den Datentyp (oder eine Komponente davon) besitzen, dh eine Instanz für Maybe MyData ist auch in Ordnung, zumindest manchmal). Innerhalb dieser Einschränkungen, wo Sie sich entscheiden, die Instanz zu setzen, ist Ihr eigenes Geschäft.

Es gibt eine weitere Ausnahme: Wenn Sie weder die Typklasse noch den Datentyp besitzen, aber eine Binärdatei und keine Bibliothek erstellen, ist das auch in Ordnung.


16
2018-06-20 23:43



(Ich weiß, dass ich zu spät zur Party komme, aber das könnte für andere noch nützlich sein)

Sie können die verwaisten Instanzen in ihrem eigenen Modul behalten, und wenn jemand dieses Modul importiert, ist es speziell, weil sie es brauchen und sie es vermeiden können, sie zu importieren, wenn sie Probleme verursachen.


5
2018-02-17 01:33



In diesem Sinne verstehe ich die Position WRT-Bibliotheken des Anti-Orphan-Instanzcamps, aber für ausführbare Ziele sollten keine verwaisten Instanzen in Ordnung sein?


3
2018-06-22 10:58