Der parametrische Polymorphismus in Haskell beruht auf der Tatsache, dass alle Werte von t :: *
Typen einheitlich als Zeiger auf ein Laufzeitobjekt dargestellt werden. Somit funktioniert derselbe Maschinencode für alle Instanziierungen von polymorphen Werten.
Kontrastpolymorphe Funktionen in Rust oder C++. Zum Beispiel hat die Identitätsfunktion dort immer noch einen Typ analog zu forall a. a -> a
, aber da Werte unterschiedlicher a
Typen unterschiedliche Größen haben können, müssen die Compiler für jede Installation einen unterschiedlichen Code erzeugen. Das bedeutet auch, dass wir nicht polymorphe Funktionen um in Runtime-Boxen passieren kann:
data Id = Id (forall a. a -> a)
da eine solche Funktion richtig für beliebige große Objekte müssten arbeiten. Es erfordert einige zusätzliche Infrastruktur, um diese Funktion zu ermöglichen, zum Beispiel könnten wir verlangen, dass eine Laufzeitfunktion forall a. a -> a
zusätzliche implizite Argumente übernimmt, die Informationen über die Größe und Konstruktoren/Destruktoren von a
Werten enthalten.
Nun ist das Problem mit newtype Vec = Vec (# Float#, Float# #)
dass, obwohl Vec
hat Art *
, Laufzeitcode, die Werte einiger t :: *
erwartet kann nicht damit umgehen. Es ist ein Stack-Allocated-Paar Floats, kein Zeiger auf ein Haskell-Objekt, und die Übergabe an Code, der erwartete Haskell-Objekte erwartet, würde zu Segfaults oder Fehlern führen.
Im Allgemeinen ist nicht unbedingt zeigergroß, so dass wir es nicht in zeigergroße Datenfelder kopieren können.
Typfamilien, die #
Typen zurückgeben, sind aus ähnlichen Gründen nicht zulässig. Beachten Sie Folgendes:
type family Foo (a :: *) :: # where
Foo Int = Int#
Foo a = (# Int#, Int# #)
data Box = forall (a :: *). Box (Foo a)
Unsere Box
ist nicht darstellbare Laufzeit, da Foo a
verschiedene Größen für unterschiedliches a
es hat. Im Allgemeinen erfordert der Polymorphismus über #
das Erzeugen von unterschiedlichem Code für verschiedene Instanziierungen, wie in Rust, aber dies interagiert schlecht mit regulärem parametrischem Polymorphismus und macht die Darstellung von polymorphen Werten zur Laufzeit schwierig, so dass GHC sich damit nicht befasst.
(Nicht sagen aber, dass eine brauchbare Implementierung unmöglich erdacht werden könnte)
"Problem mit' newtype Vec = Vec (# Float #, Float # #) 'ist das, obwohl' Vec' irgendwie '' '' "Aber warum? Warum muss es ein '' '' haben? Wenn ich 'type Vec = (# Float #, Float # #) schreibe' Vec hat '' '' '', also erwarte ich, dass newtype auch '#' hat. –
'Typ Vec = ...' ist nur ein Synonym. 'newtype' definiert nun einen neuen Typ, der sich vom Typ des umbrochenen Wertes unterscheidet. Es geschieht so, dass alle Haskell-Konstrukte zum Definieren neuer Typen als "*" zurückgegeben werden. 'newtype'-s für ungeboxte Typen würden einen großen Teil der oben erwähnten Infrastruktur erfordern, um Polymorphie über unterschiedlich große Objekte zu ermöglichen, oder sie wäre nur monomorph verwendbar, was sie im Vergleich zur Verwendung des umschlossenen Typs nicht besonders verbessert hätte . –
@ AndrásKovács Es könnte aus Art '#' gemacht werden, aber dann z. 'id :: a -> a' kann nicht auf' a ~ Vec' seit 'a :: *' spezialisiert werden. Diese Einschränkung hängt mit der Tatsache zusammen, dass die Laufzeit boxed Daten einheitlich behandelt (mit Zeigern), wenn Sie rohe, ungeboxte Daten herumreichen möchten, so dass Sie für jeden nicht gepackten Typ eine benutzerdefinierte 'id' benötigen. Nicht zu unterschiedlich zu Javas Unfähigkeit, Generika wie "T" zu haben, da "int" kein Referenztyp ist. –
chi