Haskell: Typklasse vs. Übergeben einer Funktion

16

Mir scheint, dass Sie immer Funktionsargumente übergeben können, anstatt eine Typklasse zu verwenden. Zum Beispiel, anstatt eine Gleichheitstypklasse zu definieren:

class Eq a where 
  (==)                  :: a -> a -> Bool

Die Verwendung in anderen Funktionen zur Angabe des Typarguments muss eine Instanz von Eq:

elem                    :: (Eq a) => a -> [a] -> Bool

Können wir unsere elemFunktion nicht einfach ohne Verwendung einer Typklasse definieren und stattdessen ein Funktionsargument übergeben, das den Job erledigt?

Mahdix
quelle
2
Das nennt man Wörterbuchübergabe. Sie können Typklasseneinschränkungen als implizite Argumente betrachten.
Poscat
2
Sie könnten das tun, aber es ist natürlich viel bequemer, keine Funktion übergeben zu müssen und je nach Typ nur eine "Standard" -Funktion verwenden zu müssen.
Robin Zigmond
2
Man könnte es so sagen, ja. Aber ich würde argumentieren, dass es mindestens einen weiteren wichtigen Vorteil gibt: die Fähigkeit, polymorphe Funktionen zu schreiben, die für jeden Typ funktionieren, der eine bestimmte "Schnittstelle" oder einen Satz von Merkmalen implementiert. Ich denke, Typklassenbeschränkungen drücken dies sehr deutlich aus, so dass das Übergeben zusätzlicher Funktionsargumente dies nicht tut. Insbesondere wegen der (leider impliziten) "Gesetze", die viele Typklassen erfüllen müssen. Eine Monad mEinschränkung sagt mir mehr als das Übergeben zusätzlicher Funktionsargumente vom Typ a -> m aund m a -> (a -> m b) -> m b.
Robin Zigmond
1
Mit der TypeApplicationsErweiterung können Sie das implizite Argument explizit machen. (==) @Int 3 5vergleicht 3und 5speziell als IntWerte. Sie können sich @Inteinen Schlüssel im Wörterbuch typspezifischer Gleichheitsfunktionen Intvorstellen und nicht die spezifische Vergleichsfunktion selbst.
chepner

Antworten:

19

Ja. Dies wird als "Wörterbuchübergabestil" bezeichnet. Manchmal, wenn ich einige besonders knifflige Dinge mache, muss ich eine Typklasse verschrotten und in ein Wörterbuch verwandeln, da die Wörterbuchübergabe leistungsfähiger ist 1 , aber oft recht umständlich, wodurch konzeptionell einfacher Code ziemlich kompliziert aussieht. Ich verwende manchmal den Wörterbuch-Übergabestil in Sprachen, die nicht Haskell sind, um Typklassen zu simulieren (habe aber gelernt, dass dies normalerweise keine so gute Idee ist, wie es sich anhört).

Wenn es einen Unterschied in der Ausdruckskraft gibt, gibt es natürlich einen Kompromiss. Während Sie eine bestimmte API auf mehr Arten verwenden können, wenn sie mit DPS geschrieben wurde, erhält die API weitere Informationen, wenn Sie dies nicht können. Dies zeigt sich in der Praxis unter anderem Data.Setdarin, dass es nur ein OrdWörterbuch pro Typ gibt. Das Setspeichert seine Elemente sortiert nach. OrdWenn Sie einen Satz mit einem Wörterbuch erstellen und dann ein Element mit einem anderen einfügen, wie dies mit DPS möglich wäre, können Sie die SetInvariante brechen und zum Absturz bringen. Dieses Eindeutigkeitsproblem kann mithilfe eines Phantom-Existenzials gemindert werdenGeben Sie ein, um das Wörterbuch zu markieren, aber auf Kosten einer ziemlich nervigen Komplexität in der API. Dies wird auch in der TypeableAPI ziemlich ähnlich angezeigt.

Das Einzigartigkeitsbit kommt nicht sehr oft vor. Was Typklassen großartig können, ist das Schreiben von Code für Sie. Zum Beispiel,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

Das erfordert zwei "Prozessoren", die eine Eingabe annehmen und möglicherweise eine Ausgabe geben und diese verketten, wobei sie abgeflacht werden Nothing, müsste in DPS wie folgt geschrieben werden:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

Wir mussten im Wesentlichen den Typ buchstabieren, bei dem wir ihn erneut verwenden, obwohl wir ihn bereits in der Typensignatur geschrieben haben, und selbst das war überflüssig, da der Compiler bereits alle Typen kennt. Da es nur einen Weg gibt, einen bestimmten SemigroupTyp zu erstellen, kann der Compiler dies für Sie tun. Dies hat einen Effekt vom Typ "Zinseszins", wenn Sie anfangen, viele parametrische Instanzen zu definieren und die Struktur Ihrer Typen zu verwenden, um wie in den Data.Functor.*Kombinatoren für Sie zu berechnen , und dies wird mit großer Wirkung verwendet, deriving viawenn Sie im Wesentlichen alle erhalten können "Standard" algebraische Struktur Ihres Typs für Sie geschrieben.

Und lassen Sie mich nicht einmal mit MPTCs und Fundeps anfangen, die Informationen in Typchecking und Inferenz zurückführen. Ich habe noch nie versucht, so etwas in DPS umzuwandeln - ich vermute, es würde bedeuten, viele Beweise für die Gleichstellung von Typen weiterzugeben -, aber ich bin mir auf jeden Fall sicher, dass es viel mehr Arbeit für mein Gehirn bedeuten würde, als ich mir bequem machen würde mit.

- -

1 U Nless Sie verwenden , reflectionin welchem Fall sie in Kraft äquivalent werden - aber reflectionauch umständlich zu bedienen sein.

luqui
quelle
1
Könnte
Ich bin sehr interessiert an Fundeps, die durch DPS ausgedrückt werden. Kennen Sie einige empfehlenswerte Ressourcen zu diesem Thema? Wie auch immer, sehr verständliche Erklärung.
Bob
@ Bob, nicht ohne weiteres, aber es wäre eine interessante Erkundung. Vielleicht eine neue Frage dazu stellen?
Luqui
5

Ja. Das (Dictionary Passing genannt) ist im Grunde das, was der Compiler sowieso mit Typklassen macht. Für diese Funktion würde es buchstäblich so aussehen:

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

Anrufen elemBy (==) x xsist jetzt gleichbedeutend mit elem x xs. Und in diesem speziellen Fall können Sie noch einen Schritt weiter gehen: Sie eqhaben jedes Mal das gleiche erste Argument, sodass Sie es in die Verantwortung des Anrufers legen können, dies anzuwenden, und am Ende Folgendes:

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

Anrufen elemBy2 (x ==) xsist jetzt gleichbedeutend mit elem x xs.

...Oh, Moment mal. Das ist nur so any. (Und tatsächlich in der Standardbibliothekelem = any . (==) .)

Joseph Sible-Reinstate Monica
quelle
AFAIU Dictionary Passing ist Scalas Ansatz zur Codierung von Typklassen. Diese zusätzlichen Argumente können als deklariert werden, implicitund der Compiler würde sie aus dem Gültigkeitsbereich für Sie einfügen.
michid