27 Stimmen

Was sind die Gründe dafür, dass Protokolle und Multimethoden in Clojure weniger leistungsfähig für Polymorphismus sind als Typklassen in Haskell?

Allgemeiner ausgedrückt geht es bei dieser Frage um verschiedene Ansätze zum Ausdrucksproblem. Die Idee ist, dass Ihr Programm eine Kombination aus einem Datentyp und den Operationen darüber ist. Wir möchten neue Fälle hinzufügen können, ohne die alten Klassen neu kompilieren zu müssen.

Jetzt hat Haskell einige wirklich tolle Lösungen für das Ausdrucksproblem mit dem TypeClass. Insbesondere können wir Folgendes tun:

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

member :: (Eq a) => a -> [a] -> Bool
member y [] = False
member y (x:xs) = (x == y) || member y xs

In Clojure gibt es Multimethoden - so können Sie Folgendes tun:

(defmulti area :Shape)
(defn rect [wd ht] {:Shape :Rect :wd wd :ht ht})
(defn circle [radius] {:Shape :Circle :radius radius})
(defmethod area :Rect [r]
    (* (:wd r) (:ht r)))
(defmethod area :Circle [c]
    (* (. Math PI) (* (:radius c) (:radius c))))
(defmethod area :default [x] :oops)
(def r (rect 4 13))
(def c (circle 12))
(area r)
-> 52
(area c)
-> 452.3893421169302
(area {})
-> :oops

Außerdem hat Clojure Protokolle - mit denen Sie Folgendes tun können:

(defprotocol P
  (foo [x])
  (bar-me [x] [x y]))

(deftype Foo [a b c]
  P
  (foo [x] a)
  (bar-me [x] b)
  (bar-me [x y] (+ c y)))

(bar-me (Foo. 1 2 3) 42)
=> 45

(foo
 (let [x 42]
   (reify P
     (foo [this] 17)
     (bar-me [this] x)
     (bar-me [this y] x))))

=> 17

Jetzt behauptet dieser Einzelne:

Aber es gibt Protokolle und Multimethoden. Diese sind sehr leistungsstark, aber nicht so leistungsstark wie Haskells Typklassen. Sie können etwas Ähnliches wie eine Typklasse einführen, indem Sie Ihren Vertrag in einem Protokoll spezifizieren. Dieser setzt jedoch nur auf das erste Argument, während Haskell auf der gesamten Signatur inklusive Rückgabewert setzt. Multimethoden sind leistungsstärker als Protokolle, aber nicht so leistungsstark wie Haskells Dispatch.

Meine Frage lautet: Welche Gründe gibt es dafür, dass Protokolle und Multimethoden in Clojure weniger leistungsfähig für Polymorphismus sind als Typklassen in Haskell?

25voto

Daniel Gratzer Punkte 52205

Nun, das Offensichtliche ist, dass Protokolle nur auf das erste und nur das erste Argument zugreifen können. Das bedeutet, dass sie grob äquivalent sind zu

 class Foo a where
   bar  :: a -> ...
   quux :: a -> ...
   ...

Wo a das erste Argument sein muss. Typklassen in Haskell lassen a überall in der Funktion erscheinen. Deshalb sind Protokolle leicht weniger ausdrucksstark als Typklassen.

Als Nächstes kommen Multimethoden. Multimethoden, wenn ich mich nicht irre, erlauben den Aufruf basierend auf einer Funktion aller Argumente. Dies sieht in gewisser Weise ausdrucksstärker aus als in Haskell, da Sie Argumente des gleichen Typs unterschiedlich aufrufen können. Dies kann jedoch tatsächlich in Haskell gemacht werden, indem das Argument in einen neuen Typ gewickelt wird, um es aufzurufen.

Ein paar Dinge, die meines Wissens nach nicht mit Multimethoden gemacht werden können:

  1. Aufruf basierend auf dem Rückgabetyp
  2. Speichern von Werten, die polymorph über alle Typen einer Typklasse sind forall a. Foo a => a

Um zu sehen, wie 1. ins Spiel kommt, betrachten Sie Monoid es hat einen Wert mempty :: Monoid m => m. Es ist keine Funktion, und eine Simulation davon ist mit Clojure unmöglich, da wir keine Typinformation darüber haben, welche Methode wir wählen sollen.

Für 2. betrachten Sie read :: Read a => String -> a. In Haskell könnten wir tatsächlich eine Liste erstellen, die den Typ [forall a. Read a => a] hat, wir haben die Berechnung sozusagen aufgeschoben und können jetzt Elemente der Liste ausführen und wieder ausführen, um sie als unterschiedliche Werte zu lesen.

Typklassen haben auch statische Typen, so dass überprüft wird, dass Sie nicht "festgefahren" enden, ohne eine Instanz statisch aufrufen zu können, aber Clojure ist dynamisch getypt, also schreibe ich das auf einen Unterschied im Stil zwischen den beiden Sprachen und nicht als einen speziellen Vorteil einer Seite oder der anderen. Natürlich haben Typklassen auch viel weniger Overhead als Multimethoden, da im Allgemeinen der Zeugenrekord inline geschaltet werden kann und alles statisch aufgelöst wird.

8voto

Andreas Rossberg Punkte 32540

Der grundlegendste Unterschied besteht darin, dass bei Typklassen die Anweisung an Typen und nicht an Werte erfolgt. Es wird kein Wert benötigt, um es auszuführen. Das ermöglicht wesentlich allgemeinere Fälle. Das offensichtlichste Beispiel ist die Anweisung an (Teil des) Ergebnistyps einer Funktion. Betrachten Sie z.B. die Read-Klasse von Haskell:

class Read a where
  readsPrec :: Int -> String -> [(a, String)]
  ...

Eine solche Anweisung ist offensichtlich unmöglich bei Multimethoden, die an ihren Argumenten anweisen müssen.

Siehe auch meinen umfangreicheren Vergleich mit reinem OO.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X