2015-11-17 7 views
7

Ich frage mich, warum unboxed Typen in Haskell diese Einschränkungen haben:Einschränkungen von unboxed Typen

  1. Sie kein newtype für unboxed Typ definieren:

    newtype Vec = Vec (# Float#, Float# #) 
    

    aber Sie können Typ synonim definieren:

    type Vec = (# Float#, Float# #) 
    
  2. Typfamilien können nicht ungeöffneten Typ zurückgeben:

    type family Unbox (a :: *) :: # where 
        Unbox Int = Int# 
        Unbox Word = Word# 
        Unbox Float = Float# 
        Unbox Double = Double# 
        Unbox Char = Char# 
    

Gibt es einige fundamentale Gründe dafür, oder es ist nur, weil niemand darum gebeten verfügt?

Antwort

7

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)

+1

"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. –

+1

'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 . –

+0

@ 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

4

A newtype erlauben würde, eine Klasseninstanz

instance C Vec where ... 

die für unboxed Tupel definiert werden kann nicht definiert werden. Typ-Synonyme bieten keine solche Funktionalität.

Auch Vec wäre kein Boxed-Typ. Dies bedeutet, dass Sie Typvariablen mit Vec im Allgemeinen nicht mehr instanziieren können, sofern es ihre Art nicht erlaubt. Zum Beispiel sollte [Vec] nicht erlaubt sein. Der Compiler sollte in irgendeiner Weise "normale" Newtypen und "ungefüllte" Newtypen verfolgen. Dies hat, glaube ich, den einzigen Vorteil, dass der Datenkonstruktor Vec ungepackte Werte zur Kompilierzeit einbinden kann (da sie zur Laufzeit entfernt wird). Dies würde wahrscheinlich nicht ausreichen, um die notwendigen Änderungen an der Typ-Inferenz-Engine vorzunehmen, denke ich.

+1

Oh, sollte es meine dritte Frage gewesen bin: Warum können Sie nicht Klasse über unboxed Typen wie folgt definieren: 'Klasse C (u :: #) wo ... '? –

+0

"Vec wäre kein Box-Typ" Warum muss es so sein? Geben Sie synonim 'typ Vec = (# Float #, Float # #)' hat Art '#' warum 'newtype' nicht? –

+0

"Der Compiler sollte" normale "Newtypen und" ungefüllte "Newtypen in irgendeiner Weise verfolgen." Ist regelmäßige Überprüfung nicht ausreichend? –

Verwandte Themen