Suche…


Einführung

TypeApplications sind eine Alternative Anmerkungen zu geben , wenn der Compiler kämpft Typen für einen bestimmten Ausdruck zu schließen.

Diese Reihe von Beispielen erläutert den Zweck der Erweiterung TypeApplications und deren Verwendung

Vergessen Sie nicht, die Erweiterung zu aktivieren, indem Sie {-# LANGUAGE TypeApplications #-} oben in Ihre Quelldatei {-# LANGUAGE TypeApplications #-} .

Vermeiden von Typanmerkungen

Wir verwenden Typanmerkungen, um Mehrdeutigkeiten zu vermeiden. Typenanwendungen können für denselben Zweck verwendet werden. Zum Beispiel

x :: Num a => a
x = 5

main :: IO ()
main = print x

Dieser Code hat einen Mehrdeutigkeitsfehler. Wir wissen, dass a eine Num Instanz hat, und um diese zu drucken, benötigen wir eine Show Instanz. Dies könnte funktionieren, wenn a beispielsweise ein Int war. Um den Fehler zu beheben, können wir eine Typanmerkung hinzufügen

main = print (x :: Int)

Eine andere Lösung, die Typanwendungen verwendet, würde so aussehen

main = print @Int x

Um zu verstehen, was dies bedeutet, müssen wir uns die Typensignatur des print ansehen.

print :: Show a => a -> IO ()

Die Funktion nimmt einen Parameter vom Typ a , aber eine andere Sichtweise besteht darin, dass tatsächlich zwei Parameter verwendet werden. Der erste ist ein Typ - Parameter ist, die zweite ein Wert , dessen Typ ist der erste Parameter.

Der Hauptunterschied zwischen Wertparametern und den Typparametern besteht darin, dass letztere Funktionen beim Aufruf implizit für Funktionen bereitgestellt werden. Wer stellt sie zur Verfügung? Der Typ Inferenz-Algorithmus! TypeApplications lässt uns diese Parameter explizit TypeApplications . Dies ist besonders nützlich, wenn die Typeninferenz nicht den richtigen Typ bestimmen kann.

Um das obige Beispiel aufzubrechen

print :: Show a => a -> IO ()
print @Int :: Int -> IO ()
print @Int x :: IO ()

Geben Sie Anwendungen in anderen Sprachen ein

Wenn Sie mit Sprachen wie Java, C # oder C ++ und dem Konzept von Generics / Templates vertraut sind, kann dieser Vergleich für Sie nützlich sein.

Nehmen wir an, wir haben eine generische Funktion in C #.

public static T DoNothing<T>(T in) { return in; }

Um diese Funktion mit einem float DoNothing(5.0f) können wir DoNothing(5.0f) oder wenn wir explizit sein wollen, können wir DoNothing<float>(5.0f) sagen. Dieser Teil innerhalb der spitzen Klammern ist die Typanwendung.

In Haskell ist es dasselbe, außer dass die Typparameter nicht nur auf Anrufseiten, sondern auch auf Definitionssites implizit sind.

doNothing :: a -> a
doNothing x = x

Dies kann auch explizit gemacht werden, indem Sie ScopedTypeVariables , Rank2Types oder RankNTypes Erweiterungen wie diese verwenden.

doNothing :: forall a. a -> a
doNothing x = x

Dann können wir auf der doNothing 5.0 erneut doNothing 5.0 oder doNothing @Float 5.0 schreiben

Reihenfolge der Parameter

Das Problem mit impliziten Typargumenten wird offensichtlich, sobald wir mehrere haben. In welcher Reihenfolge kommen sie?

const :: a -> b -> a

Bedeutet das Schreiben von const @Int dass a gleich Int , oder ist es b ? Wenn wir die Typparameter explizit angeben, verwenden Sie ein forall wie const :: forall a b. a -> b -> a dann ist die Reihenfolge wie geschrieben: a , dann b .

Wenn nicht, ist die Reihenfolge der Variablen von links nach rechts. Die erste zu erwähnende Variable ist der erste Typparameter, die zweite ist der zweite Typparameter und so weiter.

Was ist, wenn wir die zweite Typvariable angeben möchten, aber nicht die erste? Wir können einen Platzhalter für die erste Variable wie folgt verwenden

const @_ @Int

Der Typ dieses Ausdrucks ist

const @_ @Int :: a -> Int -> a

Interaktion mit mehrdeutigen Typen

Angenommen, Sie führen eine Klasse von Typen ein, die eine Größe in Byte haben.

class SizeOf a where
    sizeOf :: a -> Int

Das Problem ist, dass die Größe für jeden Wert dieses Typs konstant sein sollte. Wir wollen nicht , eigentlich die sizeOf Funktion abhängen a , aber nur auf seine Art.

Ohne Typanwendungen war die beste Lösung, die wir hatten, der so definierte Proxy Typ

data Proxy a = Proxy

Der Zweck dieses Typs besteht darin, Typinformationen zu tragen, jedoch keine Wertinformationen. Dann könnte unsere Klasse so aussehen

class SizeOf a where
    sizeOf :: Proxy a -> Int

Nun fragen Sie sich vielleicht, warum Sie das erste Argument überhaupt nicht fallen lassen. Der Typ unserer Funktion wäre dann nur sizeOf :: Int oder, genauer gesagt, weil es sich um eine Methode einer Klasse handelt, sizeOf :: SizeOf a => Int oder um noch expliziter sizeOf :: forall a. SizeOf a => Int .

Das Problem ist die Typinferenz. Wenn ich sizeOf irgendwo schreibe, sizeOf der Inferenzalgorithmus nur, dass ich ein Int erwartet. Sie hat keine Ahnung , welche Art ich ersetzen will a . Aus diesem Grund wird die Definition vom Compiler zurückgewiesen, es sei denn, Sie haben die Erweiterung {-# LANGUAGE AllowAmbiguousTypes #-} aktiviert. In diesem Fall kann die Definition kompiliert werden. Sie kann einfach nirgendwo ohne Mehrdeutigkeitsfehler verwendet werden.

Zum Glück spart die Einführung von Typenanwendungen den Tag! Jetzt können wir sizeOf @Int schreiben und explizit sagen, dass a Int . Typanwendungen ermöglichen es uns, einen Typparameter anzugeben, auch wenn er nicht in den tatsächlichen Parametern der Funktion enthalten ist !



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow