2017-10-13 5 views
0

Haskell dreht sich alles um Abstraktion. Aber Abstraktion kostet uns zusätzliche CPU-Zyklen und zusätzlichen Speicherverbrauch aufgrund der gemeinsamen Darstellung aller abstrakten (polymorphen) Daten - Zeiger auf Heap. Es gibt einige Möglichkeiten, um abstrakten Code mit hohen Leistungsanforderungen besser zu spielen. Soweit ich es verstehe, ist eine Art, es zu tun, Spezialisierung - im Grunde extra Code-Generierung (manuell oder durch Compiler), richtig?Haskell (GHC) Spezialtour & effiziente TypFamilien

Nehmen wir an, dass der gesamte Code unten ist Strict

Wenn wir eine Funktion sum haben (der Compiler mehr Optimierungen durchführen hilft?):

sum :: (Num a) => a -> a -> a 

Wir spezialisierte Version davon specialize mit erzeugen kann pragma:

{-#SPECIALIZE sum :: Float -> Float -> Float#-} 

Jetzt, wenn Haskell Compiler kann Bestimmen Sie zur Kompilierzeit, dass wir sum auf zwei Float s aufrufen, es wird spezialisierte Version davon verwenden. Keine Heap-Allokationen, oder?

Funktionen - fertig. Dasselbe Pragma kann auf Klasseninstanzen angewendet werden. Logik ändert sich hier nicht, oder?

Aber was ist mit Datentypen? Ich vermute, dass TypeFamilies hier zuständig sind?

Lassen Sie uns versuchen, abhängige längenindexierte Liste zu spezialisieren.

--UVec for unboxed vector 
class UVec a where 
    data Vec (n :: Nat) a :: * 

instance UVec Float where 
    data Vec n Float where 
    VNilFloat :: Vec 0 Float 
    VConsFloat :: {-#UNPACK#-}Float -> 
        Vec n Float -> 
        Vec (N :+ 1) Float 

Aber Vec hat ein Problem. Wir können keine Mustervergleiche für seine Konstruktoren erstellen, da jede Instanz von UVecVec nicht mit identischen Konstruktoren bereitstellen muss. Dies zwingt uns, jede Funktion auf Vec für jede Instanz von Vec zu implementieren (da ein Mangel an Musterübereinstimmung impliziert, dass es unter Vec nicht polymorph sein kann). Was ist in diesem Fall die beste Vorgehensweise?

Antwort

1

Wie Sie sagen, können wir nicht Muster übereinstimmen auf UVec a ohne zu wissen, was a ist. Eine Option besteht darin, eine andere Typklasse zu verwenden, die Ihre Vektorklasse um eine benutzerdefinierte Funktion erweitert.

class UVec a => UVecSum a where 
    sum :: UVec a -> a 

instance UVecSum Float where 
    sum = ... -- use pattern match here 

Wenn später verwenden wir sum v wo v :: UVec Float, die Float -spezifische Code, den wir in der Instanz definiert wird aufgerufen.

0

Teilweise Antwort, aber vielleicht könnte es helfen.

Soweit ich verstehe, ist eine Möglichkeit, es ist Spezialisierung - grundsätzlich extra Code-Generierung (manuell oder durch Compiler), richtig?

Ja, dies ähnelt der Codeinstanz in C++ - Vorlagen.

Wenn der Haskell-Compiler zur Kompilierzeit bestimmen kann, dass wir die Summe auf zwei Floats aufrufen, wird eine spezialisierte Version davon verwendet. Keine Heap-Allokationen, oder?

Ja, der Compiler ruft wann immer möglich die spezialisierte Version auf. Nicht sicher, was Sie bezüglich der Heapzuweisungen meinen.

In Bezug auf die abhängigen Typen Vektoren: normalerweise (ich kenne das von Idris) wird die Länge des Vektors vom Compiler nach Möglichkeit eliminiert. Es ist für eine stärkere Typprüfung vorgesehen. Zur Laufzeit ist die Längeninformation nutzlos und kann verworfen werden.

+1

Informationen zur Laufzeitlänge: haskell hat keine vollständigen abhängigen Typen. Haskell unterscheidet zwischen Laufzeitwerten und Typen. Die Länge des Vec wird also garantiert gelöscht (da die Länge ein Typ (oder eine Art) ist, kein Laufzeitwert, aber haskell erlaubt nicht, dass Typen zur Laufzeit beibehalten werden). – russoulmc

Verwandte Themen