Warum (oder warum nicht) werden existenzielle Typen in der funktionalen Programmierung als schlechte Praxis angesehen?

43

Mit welchen Techniken kann ich Code konsequent umgestalten, ohne auf existenzielle Typen angewiesen zu sein? In der Regel werden diese verwendet, um unerwünschte Konstruktionen Ihres Typs zu disqualifizieren und den Konsum mit einem Minimum an Wissen über den angegebenen Typ zu ermöglichen (oder so ist mein Verständnis).

Hat jemand eine einfache, konsistente Methode gefunden, um das Vertrauen in diesen Code zu beseitigen, bei der einige der Vorteile erhalten bleiben? Oder zumindest eine Möglichkeit, in eine Abstraktion zu schlüpfen, die deren Entfernung ermöglicht, ohne dass eine erhebliche Codeabwanderung erforderlich ist, um mit der Änderung fertig zu werden?

Sie können mehr über existentielle Typen lesen Sie hier ( „wenn du dich traust ..“).

Petr Pudlák
quelle
8
@RobertHarvey Ich habe den Blog-Beitrag gefunden, der mich zum ersten Mal über diese Frage nachdenken ließ: Haskell Antipattern: Existential Typeclass . Außerdem beschreibt der Aufsatz, den Joey Adams erwähnt, in Abschnitt 3.1 einige Probleme mit existenziellen Merkmalen. Wenn Sie gegenteilige Argumente haben, teilen Sie diese bitte mit.
Petr Pudlák
5
@PetrPudlák: Denken Sie daran, dass das Antimuster im Allgemeinen keine existenziellen Typen sind, sondern eine besondere Verwendung, wenn etwas Einfacheres und Einfacheres (und besser in Haskell unterstütztes) die gleiche Aufgabe erledigen würde.
CA McCann
10
Wenn Sie wissen möchten, warum der Autor eines bestimmten Blogposts eine Meinung geäußert hat, sollten Sie wahrscheinlich den Autor fragen.
Eric Lippert
6
@Ptolemy Das ist Ansichtssache. Mit Haskell kann ich mir seit Jahren kaum vorstellen, eine funktionale Sprache zu verwenden, die kein starkes Typensystem hat.
Petr Pudlák
4
@Ptolemy: Haskells Mantra lautet "Wenn es kompiliert, funktioniert es", Erlangs Mantra lautet "Lass es abstürzen". Dynamisches Tippen ist gut als Klebstoff, aber ich persönlich würde Dinge nicht nur mit dem Klebstoff bauen.
Den

Antworten:

8

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 ShowInstanzen 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:

  1. Die einzige nützliche Operation, die Sie an einem ausführen können, AnyShapeist das Abrufen seiner Fläche.
  2. Sie müssen weiterhin den AnyShapeKonstruktor verwenden, um einen der Formtypen in den AnyShapeTyp 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 ShapeTyps sind und Sie schreiben Funktionen, um Ihre Kreise und Quadrate in Shapes 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 AnyShapeArt von oben und Doubleist 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 STTyp 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.

Sacundim
quelle
Das ist kein Isomorphismus.
Ryan Reich
2

Ich bin mit Haskell nicht allzu vertraut, daher werde ich versuchen, den allgemeinen Teil der Frage als nicht-akademischer funktionaler C # -Entwickler zu beantworten.

Nach einigem Lesen stellt sich heraus, dass:

  1. Java-Platzhalter ähneln existenziellen Typen:

    Unterschied zwischen Scalas existenziellen Typen und Javas Wildcard anhand eines Beispiels

  2. Platzhalter werden in C # nicht vollständig implementiert: Die generische Varianz wird unterstützt, die Varianz der Aufrufsite jedoch nicht:

    C # -Generics: Platzhalter

  3. Möglicherweise benötigen Sie diese Funktion nicht jeden Tag, aber wenn Sie dies tun, werden Sie es spüren (z. B. müssen Sie einen zusätzlichen Typ einführen, damit die Dinge funktionieren):

    Platzhalter in generischen C # -Einschränkungen

Basierend auf diesen Informationen sind existenzielle Typen / Platzhalter nützlich, wenn sie ordnungsgemäß implementiert wurden, und es gibt an sich nichts Falsches daran, aber sie können wahrscheinlich genauso wie andere Sprachfunktionen missbraucht werden.

Den
quelle