Ist es möglich, eine Haskell-Funktion zu schreiben, die einen parametrisierten Typ liefert, in dem der genaue Typparameter verborgen ist? I.e. etwas wie f :: T -> (exists a. U a)
? Der naheliegende Versuch:
kann nicht mit:
kompiliert werden %Vor% Ich weiß, dass dies ein künstliches Beispiel ist, aber ich bin neugierig, ob es GHC gelingt, den exakten Typ, mit dem D
parametrisiert ist, nicht zu interessieren, ohne einen weiteren existenziellen Wrapper-Typ für das Ergebnis einzuführen von unwrap
.
Zur Klarstellung möchte ich Sicherheit schreiben, möchte aber auch eine Funktion dToString :: D a -> String
anwenden können, die a
nicht interessiert (zB weil sie einfach ein String-Feld aus D
extrahiert) Ergebnis von unwrap
. Ich weiß, dass es andere Wege gibt, um das zu erreichen (z. B. wrapToString (Wrap d) = dToString d
definieren), aber ich bin mehr daran interessiert, ob es einen fundamentalen Grund dafür gibt, warum solch ein Verstecken unter Existenziellen nicht erlaubt ist.
Ja, Sie können GHC davon überzeugen, dass Sie sich nicht um den genauen Typ kümmern, mit dem D
parametrisiert ist. Nur, es ist eine schreckliche Idee.
Das passiert, wenn ich dieses einfache Programm ausführe:
usw., bis der Speicherverbrauch das System zum Absturz bringt.
Typen sind wichtig! Wenn Sie das Typsystem umgehen, umgehen Sie die Vorhersehbarkeit Ihres Programms (dh alles kann passieren, einschließlich thermonuklearer Krieg oder der berühmte Dämonen fliegen aus deiner Nase ).
Sie dachten offensichtlich, dass Typen irgendwie anders funktionieren. In dynamischen Sprachen wie Python und auch in OO-Sprachen wie Java ist ein Typ gewissermaßen eine Eigenschaft , die ein Wert haben kann. Die (Referenz-) Werte enthalten also nicht nur die Informationen, die zur Unterscheidung verschiedener Werte eines einzelnen Typs benötigt werden, sondern auch Informationen zur Unterscheidung verschiedener (Unter-) Typen. Das ist in vielerlei Hinsicht ziemlich ineffizient - es ist ein Hauptgrund, warum Python so langsam ist und Java eine so große VM braucht.
In Haskell gibt es zur Laufzeit nicht . Eine Funktion weiß nie, mit welchem Typ die Werte arbeiten. Nur weil der Compiler alles über die Typen weiß, die er haben wird, braucht die Funktion kein solches Wissen - der Compiler hat es bereits fest programmiert! (Das heißt, es sei denn, Sie umgehen es mit unsafeCoerce
, was, wie ich gezeigt habe, genauso unsicher ist, wie es sich anhört.)
Wenn Sie den Typ als "Eigenschaft" an einen Wert anhängen möchten, müssen Sie dies explizit tun, und dafür gibt es diese existenziellen Wrapper. Es gibt jedoch normalerweise bessere Möglichkeiten, dies in einer funktionalen Sprache zu tun. Für was ist wirklich die Anwendung, für die Sie das wollten?
Vielleicht ist es auch hilfreich, sich daran zu erinnern, was eine Signatur mit polymorphem Ergebnis bedeutet. unwrap :: Wrap -> D a
bedeutet nicht "das Ergebnis ist einige D a
... und der Aufrufer kümmert sich nicht mehr um den a
used". Das wäre in Java der Fall, aber in Haskell wäre es ziemlich nutzlos, weil man mit einem Wert unbekannten Typs nichts anfangen kann.
Stattdessen bedeutet es: für was auch immer Typ a
der Aufrufer anfordert, diese Funktion kann einen geeigneten D a
-Wert liefern. Natürlich ist das schwierig zu liefern - ohne zusätzliche Informationen ist es genauso unmöglich wie irgendetwas mit einem unbekannten Wert zu tun. Aber wenn in den Argumenten bereits a
-Werte vorhanden sind oder a
irgendwie auf eine Typklasse beschränkt ist (z. B. fromInteger :: Num a => Integer -> a
, dann ist das durchaus möglich und sehr nützlich.
Um ein String
-Feld zu erhalten - unabhängig vom Parameter a
- können Sie direkt mit dem umbrochenen Wert arbeiten:
Um solche Funktionen auf Wrap
allgemeiner zu schreiben (mit jedem "Label-Accessor, der sich nicht um a
kümmert"), verwenden Sie Rank2-Polymorphismus, wie in der Antwort von n.m. gezeigt.
Tags und Links haskell existential-type