Haskell Language
Allgemeine GHC-Spracherweiterungen
Suche…
Bemerkungen
Diese Spracherweiterungen sind normalerweise verfügbar, wenn der Glasgow Haskell Compiler (GHC) verwendet wird, da sie nicht Bestandteil des genehmigten Haskell 2010-Sprachberichts sind . Um diese Erweiterungen zu verwenden, muss man entweder den Compiler mit einem Flag informieren oder ein LANGUAGE
Programm vor dem module
in einer Datei platzieren. Die offizielle Dokumentation befindet sich in Abschnitt 7 des GCH-Benutzerhandbuchs.
Das Format des LANGUAGE
Programms ist {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-}
. Dies ist das Literal {-#
gefolgt von LANGUAGE
gefolgt von einer durch Kommas getrennten Liste von Erweiterungen und schließlich dem schließenden #-}
. Mehrere LANGUAGE
Programme können in einer Datei gespeichert werden.
MultiParamTypeClasses
Es ist eine sehr verbreitete Erweiterung, die Typenklassen mit mehreren Typparametern ermöglicht. Sie können sich MPTC als eine Beziehung zwischen Typen vorstellen.
{-# LANGUAGE MultiParamTypeClasses #-}
class Convertable a b where
convert :: a -> b
instance Convertable Int Float where
convert i = fromIntegral i
Die Reihenfolge der Parameter ist wichtig.
MPTCs können manchmal durch Typfamilien ersetzt werden.
FlexibleInstanzen
Regelmäßige Fälle erfordern:
All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Das bedeutet, dass Sie beispielsweise, während Sie eine Instanz für [a]
erstellen können, keine Instanz für speziell [Int]
erstellen können.; FlexibleInstances
entspannt das:
class C a where
-- works out of the box
instance C [a] where
-- requires FlexibleInstances
instance C [Int] where
ÜberladeneStrings
Normalerweise haben Stringliterale in Haskell einen Typ von String
(einen Typalias für [Char]
). Während dies für kleinere Bildungsprogramme kein Problem darstellt, benötigen reale Anwendungen häufig effizienteren Speicher wie Text
oder ByteString
.
OverloadedStrings
ändert einfach den Typ der Literale in
"test" :: Data.String.IsString a => a
Sie können direkt an Funktionen übergeben werden, die einen solchen Typ erwarten. Viele Bibliotheken implementieren diese Schnittstelle für ihre zeichenfolgenartigen Typen, einschließlich Data.Text und Data.ByteString, die sowohl Zeit- als auch Platzvorteile gegenüber [Char]
bieten.
OverloadedStrings
gibt es auch einige einzigartige Anwendungen, wie die aus der einfachen Postgresql- Bibliothek. SQL-Abfragen können wie Anführungszeichen in doppelte Anführungszeichen geschrieben werden, bieten jedoch Schutz vor falscher Verkettung, einer bekannten Quelle für SQL-Injection-Angriffe.
Um eine Instanz der IsString
Klasse zu erstellen, müssen Sie die fromString
Funktion fromString
. Beispiel † :
data Foo = A | B | Other String deriving Show
instance IsString Foo where
fromString "A" = A
fromString "B" = B
fromString xs = Other xs
tests :: [ Foo ]
tests = [ "A", "B", "Testing" ]
† Dieses Beispiel wurde mit freundlicher Genehmigung von Lyndon Maydwell ( sordina
auf GitHub) hier gefunden.
TupleSections
Eine syntaktische Erweiterung, mit der der Tupelkonstruktor (der einen Operator darstellt) abschnittsweise angewendet wird:
(a,b) == (,) a b
-- With TupleSections
(a,b) == (,) a b == (a,) b == (,b) a
N-Tupel
Es funktioniert auch für Tupel, deren Arity größer als zwei ist
(,2,) 1 3 == (1,2,3)
Kartierung
Dies kann an anderen Stellen nützlich sein, an denen Abschnitte verwendet werden:
map (,"tag") [1,2,3] == [(1,"tag"), (2, "tag"), (3, "tag")]
Das obige Beispiel ohne diese Erweiterung würde folgendermaßen aussehen:
map (\a -> (a, "tag")) [1,2,3]
UnicodeSyntax
Eine Erweiterung, mit der Sie Unicode-Zeichen anstelle bestimmter integrierter Operatoren und Namen verwenden können.
ASCII | Unicode | Verwendet) |
---|---|---|
:: | ∷ | hat Typ |
-> | → | Funktionstypen, Lambdas case Zweige etc. |
=> | ⇒ | Klasseneinschränkungen |
forall | ∀ | expliziter Polymorphismus |
<- | ← | do - Notation |
* | ★ | die Art (oder Art) der Typen (z. B. Int :: ★ ) |
>- | ⤚ | proc Notation für Arrows |
-< | ⤙ | proc Notation für Arrows |
>>- | ⤜ | proc Notation für Arrows |
-<< | ⤛ | proc Notation für Arrows |
Zum Beispiel:
runST :: (forall s. ST s a) -> a
würde werden
runST ∷ (∀ s. ST s a) → a
Beachten Sie, dass das Beispiel *
vs. ★
etwas anders ist: Da *
nicht reserviert ist, funktioniert ★
ebenso wie *
für die Multiplikation oder jede andere Funktion (*)
und umgekehrt. Zum Beispiel:
ghci> 2 ★ 3
6
ghci> let (*) = (+) in 2 ★ 3
5
ghci> let (★) = (-) in 2 * 3
-1
BinaryLiterals
Standard Haskell ermöglicht das Schreiben von Ganzzahl-Literalen in Dezimalzahlen (ohne Präfix), Hexadezimalzahlen (vorangestellt mit 0x
oder 0X
) und oktal (vorangestellt mit 0o
oder 0O
). Die BinaryLiterals
Erweiterung fügt die Option für binär (vorangestellt mit 0b
oder 0B
) hinzu.
0b1111 == 15 -- evaluates to: True
Existenzquantifizierung
Dies ist eine Typsystemerweiterung, die Typen zulässt, die existentiell quantifiziert werden, oder mit anderen Worten, Typvariablen, die nur zur Laufzeit instanziiert werden † .
Ein Wert eines existentiellen Typs ähnelt einem abstrakten Basisklasse-Verweis in OO-Sprachen: Sie kennen den genauen Typ nicht, der in type enthalten ist, aber Sie können die Klasse der Typen einschränken.
data S = forall a. Show a => S a
oder äquivalent mit der GADT-Syntax:
{-# LANGUAGE GADTs #-}
data S where
S :: Show a => a -> S
Existentielle Typen die Tür öffnen , um Dinge wie fast heterogener Behälter: wie oben gesagt, es gibt tatsächlich verschiedene Arten in einen sein kann S
- Wert, aber alle von ihnen können show
n, daher können Sie auch tun
instance Show S where
show (S a) = show a -- we rely on (Show a) from the above
Jetzt können wir eine Sammlung solcher Objekte erstellen:
ss = [S 5, S "test", S 3.0]
Damit können wir auch das polymorphe Verhalten nutzen:
mapM_ print ss
Existentiale können sehr mächtig sein, aber beachten Sie, dass sie in Haskell nicht sehr oft notwendig sind. Im obigen Beispiel können Sie mit der Show
Instanz eigentlich nur die Werte anzeigen (duh!), Dh eine Zeichenfolgendarstellung erstellen. Der gesamte S
Typ enthält daher genau so viele Informationen wie der String, den Sie beim Anzeigen erhalten. Daher ist es in der Regel besser, diese Saite einfach sofort zu speichern, zumal Haskell faul ist und die Saite daher ohnehin nur ein unbewerteter Thunk ist.
Auf der anderen Seite verursachen Existenziale einige einzigartige Probleme. Zum Beispiel ist die Art und Weise, in der die Typinformationen in einem existentiellen Bereich verborgen sind. Wenn Sie einen S
Wert mit einem Muster abgleichen, haben Sie den enthaltenen Typ im Gültigkeitsbereich (genauer gesagt die Show
Instanz), aber diese Informationen können niemals ihren Gültigkeitsbereich verlassen, was zu einer Art "Geheimgesellschaft" wird: dem Compiler nichts entgeht dem Geltungsbereich außer Werten, deren Typ von außen bereits bekannt ist. Dies kann zu seltsamen Fehlern führen, da der Couldn't match type 'a0' with '()' 'a0' is untouchable
.
† Stellen Sie dies dem gewöhnlichen parametrischen Polymorphismus gegenüber, der im Allgemeinen zur Kompilierzeit aufgelöst wird (wodurch das vollständige Löschen des Typs ermöglicht wird).
Existentielle Typen unterscheiden sich von Rang-N - Typen - diese Erweiterungen sind, grob gesagt, zwei zueinander: tatsächlich Werte einer existentiellen Art verwenden, benötigen Sie einen (möglicherweise constrained-) polymorphe Funktion, wie show
im Beispiel. Eine polymorphe Funktion ist universell quantifiziert, dh sie funktioniert für jeden Typ in einer gegebenen Klasse, während existenzielle Quantifizierung bedeutet, dass sie für einen bestimmten Typ funktioniert, der a priori unbekannt ist. Wenn Sie eine polymorphe Funktion haben, reicht dies aus, um jedoch polymorphe Funktionen als Argumente zu übergeben, benötigen Sie {-# LANGUAGE Rank2Types #-}
:
genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)
LambdaCase
Eine syntaktische Erweiterung, mit der Sie \case
anstelle von \arg -> case arg of
schreiben können.
Betrachten Sie die folgende Funktionsdefinition:
dayOfTheWeek :: Int -> String
dayOfTheWeek 0 = "Sunday"
dayOfTheWeek 1 = "Monday"
dayOfTheWeek 2 = "Tuesday"
dayOfTheWeek 3 = "Wednesday"
dayOfTheWeek 4 = "Thursday"
dayOfTheWeek 5 = "Friday"
dayOfTheWeek 6 = "Saturday"
Wenn Sie vermeiden möchten, den Funktionsnamen zu wiederholen, können Sie Folgendes schreiben:
dayOfTheWeek :: Int -> String
dayOfTheWeek i = case i of
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
Mit der Erweiterung LambdaCase können Sie dies als Funktionsausdruck schreiben, ohne das Argument benennen zu müssen:
{-# LANGUAGE LambdaCase #-}
dayOfTheWeek :: Int -> String
dayOfTheWeek = \case
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
RankNTypes
Stellen Sie sich die folgende Situation vor:
foo :: Show a => (a -> String) -> String -> Int -> IO ()
foo show' string int = do
putStrLn (show' string)
putStrLn (show' int)
Hier möchten wir eine Funktion übergeben, die einen Wert in einen String konvertiert, diese Funktion auf einen String-Parameter und einen Int-Parameter anwendet und beide druckt. Meiner Meinung nach gibt es keinen Grund, warum dies versagen sollte! Wir haben eine Funktion, die für beide Arten der Parameter funktioniert, die wir übergeben.
Leider wird das nicht überprüft! GHC folgert die a
Typ basiert weg von seinem ersten Auftreten im Funktionskörper. Das heißt, sobald wir treffen:
putStrLn (show' string)
GHC wird auf die show' :: String -> String
, da string
ein String
. Beim Versuch, show' int
.
RankNTypes
können Sie die RankNTypes
stattdessen wie folgt schreiben und alle Funktionen quantifizieren, die den Typ show'
erfüllen:
foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()
Dies ist Rang 2 Polymorphismus: Wir behaupten, dass die Funktion show'
für alle a
innerhalb unserer Funktion funktionieren muss, und die vorherige Implementierung funktioniert nun.
Die Erweiterung RankNTypes
ermöglicht das willkürliche Verschachteln von forall ...
Blöcken in RankNTypes
. Mit anderen Worten, es erlaubt Rang-N-Polymorphismus.
Überladene Listen
hinzugefügt in GHC 7.8 .
Mit OverloadedLists, ähnlich wie OverloadedStrings , können Listenliterale wie folgt desugiert werden:
[] -- fromListN 0 []
[x] -- fromListN 1 (x : [])
[x .. ] -- fromList (enumFrom x)
Dies ist praktisch, wenn Sie mit Typen wie Set
, Vector
und Map
s arbeiten.
['0' .. '9'] :: Set Char
[1 .. 10] :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z'] :: Text
IsList
Klasse in GHC.Exts
soll mit dieser Erweiterung verwendet werden.
IsList
ist mit einer IsList
, Item
und drei Funktionen, fromList :: [Item l] -> l
, toList :: l -> [Item l]
und fromListN :: Int -> [Item l] -> l
fromListN
ist optional. Typische Implementierungen sind:
instance IsList [a] where
type Item [a] = a
fromList = id
toList = id
instance (Ord a) => IsList (Set a) where
type Item (Set a) = a
fromList = Set.fromList
toList = Set.toList
Beispiele aus OverloadedLists - GHC .
Funktionale Abhängigkeiten
Wenn Sie eine Typenklasse mit mehreren Parametern mit den Argumenten a, b, c und x haben, können Sie mit dieser Erweiterung ausdrücken, dass der Typ x eindeutig aus a, b und c identifiziert werden kann:
class SomeClass a b c x | a b c -> x where ...
Wenn eine Instanz einer solchen Klasse deklariert wird, wird sie gegen alle anderen Instanzen geprüft, um sicherzustellen, dass die funktionale Abhängigkeit gilt, d. H. Es gibt keine andere Instanz mit demselben abc
sondern einem anderen x
.
Sie können mehrere Abhängigkeiten in einer durch Kommas getrennten Liste angeben:
class OtherClass a b c d | a b -> c d, a d -> b where ...
In MTL sehen wir zum Beispiel:
class MonadReader r m| m -> r where ...
instance MonadReader r ((->) r) where ...
Wenn Sie einen Wert vom Typ MonadReader a ((->) Foo) => a
, kann der Compiler auf a ~ Foo
, da das zweite Argument das erste vollständig bestimmt und den Typ entsprechend vereinfacht.
Die SomeClass
Klasse kann als eine Funktion der Argumente abc
, die zu x
. Mit solchen Klassen können Berechnungen im Typsystem durchgeführt werden.
GADTs
Herkömmliche algebraische Datentypen sind in ihren Typvariablen parametrisch. Zum Beispiel, wenn wir ein ADT wie definieren
data Expr a = IntLit Int
| BoolLit Bool
| If (Expr Bool) (Expr a) (Expr a)
mit der Hoffnung, dass dadurch nicht gut typisierte Bedingungen statisch ausgeschlossen werden, verhält sich dies nicht wie erwartet, da der Typ von IntLit :: Int -> Expr a
quantifiziert wird: Für jede Auswahl von a
wird ein Wert vom Typ erzeugt Expr a
. Für a ~ Bool
haben wir insbesondere IntLit :: Int -> Expr Bool
, wodurch wir etwas wie If (IntLit 1) e1 e2
konstruieren können. If (IntLit 1) e1 e2
ist, was der Typ des If
Konstruktors ausschließen wollte.
Mit generalisierten algebraischen Datentypen können wir den resultierenden Typ eines Datenkonstruktors so steuern, dass er nicht nur parametrisch ist. Wir können unseren Expr
Typ wie folgt als GADT umschreiben:
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
If :: Expr Bool -> Expr a -> Expr a -> Expr a
Hier ist der Typ des Konstruktors IntLit
Int -> Expr Int
, und IntLit 1 :: Expr Bool
prüft also nicht.
Der Musterabgleich bei einem GADT-Wert bewirkt eine Verfeinerung des Typs des zurückgegebenen Begriffs. Zum Beispiel ist es möglich, einen Evaluator für Expr a
wie folgt zu schreiben:
crazyEval :: Expr a -> a
crazyEval (IntLit x) =
-- Here we can use `(+)` because x :: Int
x + 1
crazyEval (BoolLit b) =
-- Here we can use `not` because b :: Bool
not b
crazyEval (If b thn els) =
-- Because b :: Expr Bool, we can use `crazyEval b :: Bool`.
-- Also, because thn :: Expr a and els :: Expr a, we can pass either to
-- the recursive call to `crazyEval` and get an a back
crazyEval $ if crazyEval b then thn else els
Beachten Sie, dass wir in den obigen Definitionen (+)
können, denn wenn zB IntLit x
mit einem Muster übereinstimmt, erfahren wir auch, dass a ~ Int
(und ebenfalls für not
und if_then_else_
wenn a ~ Bool
).
ScopedTypeVariables
ScopedTypeVariables
Sie innerhalb einer Deklaration auf allgemein quantifizierte Typen verweisen. Um genauer zu sein:
import Data.Monoid
foo :: forall a b c. (Monoid b, Monoid c) => (a, b, c) -> (b, c) -> (a, b, c)
foo (a, b, c) (b', c') = (a :: a, b'', c'')
where (b'', c'') = (b <> b', c <> c') :: (b, c)
Wichtig ist, dass wir den Compiler mit a
, b
und c
in Unterausdrücken der Deklaration (das Tupel in der where
Klausel und das erste a
im Endergebnis) anweisen können. In der Praxis ScopedTypeVariables
beim Schreiben komplexer Funktionen als Summe von Teilen, sodass der Programmierer Zwischenwerten ohne konkrete Typen ScopedTypeVariables
hinzufügen kann.
PatternSynonyme
Mustersynonyme sind Abstraktionen von Mustern, ähnlich wie Funktionen Abstraktionen von Ausdrücken sind.
In diesem Beispiel betrachten wir die Schnittstelle, die Data.Sequence
, und sehen, wie sie mit Data.Sequence
verbessert werden kann. Der Seq
Typ ist ein Datentyp, der intern eine komplizierte Darstellung verwendet , um eine gute asymptotische Komplexität für verschiedene Operationen zu erzielen, vor allem für O (1) (un) consing und (un) snocing.
Diese Darstellung ist jedoch unhandlich und einige ihrer Invarianten können nicht in Haskells Typensystem ausgedrückt werden. Aus diesem Grund wird der Seq
Typ Benutzern als abstrakter Typ angezeigt, zusammen mit invarianterhaltenden Zugriffs- und Konstruktorfunktionen.
empty :: Seq a
(<|) :: a -> Seq a -> Seq a
data ViewL a = EmptyL | a :< (Seq a)
viewl :: Seq a -> ViewL a
(|>) :: Seq a -> a -> Seq a
data ViewR a = EmptyR | (Seq a) :> a
viewr :: Seq a -> ViewR a
Die Verwendung dieser Schnittstelle kann jedoch etwas umständlich sein:
uncons :: Seq a -> Maybe (a, Seq a)
uncons xs = case viewl xs of
x :< xs' -> Just (x, xs')
EmptyL -> Nothing
Wir können Ansichtsmuster verwenden, um es etwas aufzuräumen:
{-# LANGUAGE ViewPatterns #-}
uncons :: Seq a -> Maybe (a, Seq a)
uncons (viewl -> x :< xs) = Just (x, xs)
uncons _ = Nothing
Mit der PatternSynonyms
können wir eine noch schönere Benutzeroberfläche bieten, indem wir Pattern Matching zulassen, um so zu tun, als hätten wir eine Cons- oder Snoc-Liste:
{-# LANGUAGE PatternSynonyms #-}
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
pattern Empty :: Seq a
pattern Empty <- (Seq.viewl -> Seq.EmptyL)
pattern (:<) :: a -> Seq a -> Seq a
pattern x :< xs <- (Seq.viewl -> x Seq.:< xs)
pattern (:>) :: Seq a -> a -> Seq a
pattern xs :> x <- (Seq.viewr -> xs Seq.:> x)
Dies erlaubt uns, uncons
in einem sehr natürlichen Stil zu schreiben:
uncons :: Seq a -> Maybe (a, Seq a)
uncons (x :< xs) = Just (x, xs)
uncons _ = Nothing
RecordWildCards
Siehe RecordWildCards