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



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