Existenzielle Typen gelten in der funktionalen Programmierung nicht als schlechte Praxis. Ich denke, was Sie auslöst, ist, dass eine der am häufigsten zitierten Verwendungen für Existentials das existentielle Typenklassen-Antimuster ist , das viele Leute für eine schlechte Praxis halten.
Dieses Muster wird häufig als Antwort auf die Frage ausgegeben, wie eine Liste heterogen typisierter Elemente erstellt werden soll, die alle dieselbe Typklasse implementieren. Möglicherweise möchten Sie eine Liste von Werten mit Show
Instanzen haben:
{-# LANGUAGE ExistentialTypes #-}
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
area (AnyShape x) = area x
example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]
Das Problem mit Code wie diesem ist folgendes:
- Die einzige nützliche Operation, die Sie an einem ausführen können,
AnyShape
ist das Abrufen seiner Fläche.
- Sie müssen weiterhin den
AnyShape
Konstruktor verwenden, um einen der Formtypen in den AnyShape
Typ zu bringen.
Wie sich herausstellt, bringt Ihnen dieser Code nicht wirklich etwas, was dieser kürzere Code nicht bringt:
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]
Bei Klassen mit mehreren Methoden kann derselbe Effekt im Allgemeinen einfacher durch Verwendung einer Codierung mit "Methodenaufzeichnungen" erzielt werden. Anstatt eine Typklasse zu verwenden Shape
, definieren Sie einen Datensatztyp, dessen Felder die "Methoden" des Shape
Typs sind und Sie schreiben Funktionen, um Ihre Kreise und Quadrate in Shape
s umzuwandeln .
Das heißt aber nicht, dass existenzielle Typen ein Problem sind! In Rust gibt es beispielsweise eine Funktion namens Merkmalsobjekte , die häufig als existenzieller Typ über einem Merkmal beschrieben werden (Rusts Versionen von Typklassen). Wenn existenzielle Typenklassen in Haskell ein Gegenmuster sind, bedeutet das, dass Rust eine schlechte Lösung ausgewählt hat? Nein! Die Motivation in der Haskell-Welt liegt in der Syntax und Bequemlichkeit, nicht wirklich im Prinzip.
Ein mathematischer Weg , dies zu setzen wird darauf hingewiesen , dass die AnyShape
Art von oben und Double
ist isomorph -e eine „lossless Umwandlung“ zwischen ihnen ist (na ja, außer für Punkt precision floating):
forward :: AnyShape -> Double
forward = area
backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))
Genau genommen gewinnt oder verliert man keine Macht, wenn man sich für eine gegen die andere entscheidet. Dies bedeutet, dass die Auswahl auf anderen Faktoren wie Benutzerfreundlichkeit oder Leistung basieren sollte.
Denken Sie auch daran, dass existenzielle Typen außerhalb dieses Beispiels für heterogene Listen andere Verwendungszwecke haben. Es ist also gut, sie zu haben. Beispielsweise verwendet der ST
Typ von Haskell , der es uns ermöglicht, Funktionen zu schreiben, die äußerlich rein sind, aber intern Speichermutationsoperationen verwenden, eine Technik, die auf existentiellen Typen basiert, um die Sicherheit beim Kompilieren zu gewährleisten.
Die allgemeine Antwort lautet also, dass es keine allgemeine Antwort gibt. Verwendungen existenzieller Typen können nur im Kontext beurteilt werden - und die Antworten können unterschiedlich sein, je nachdem, welche Funktionen und Syntax von verschiedenen Sprachen bereitgestellt werden.