Haskell Language
Typ Anwendung
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 !