Zoeken…


Opmerkingen

Deze taalextensies zijn meestal beschikbaar wanneer u de Glasgow Haskell Compiler (GHC) gebruikt, omdat ze geen deel uitmaken van het goedgekeurde taalrapport Haskell 2010 . Om deze extensies te gebruiken, moet men ofwel de compiler te informeren met behulp van een vlag of plaats een LANGUAGE programa voor de module zoekwoord in een bestand. De officiële documentatie is te vinden in sectie 7 van de GCH-gebruikershandleiding.

Het formaat van het LANGUAGE programma is {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-} . Dat is de letterlijke {-# gevolgd door LANGUAGE gevolgd door een door komma's gescheiden lijst met extensies, en ten slotte de afsluitende #-} . Meerdere LANGUAGE programma's kunnen in één bestand worden geplaatst.

MultiParamTypeClasses

Het is een veel voorkomende extensie die typeklassen met meerdere typeparameters mogelijk maakt. Je kunt MPTC beschouwen als een relatie tussen typen.

{-# LANGUAGE MultiParamTypeClasses #-}

class Convertable a b where
    convert :: a -> b

instance Convertable Int Float where
    convert i = fromIntegral i

De volgorde van parameters is belangrijk.

MPTC's kunnen soms worden vervangen door type families.

FlexibleInstances

Regelmatige instanties vereisen:

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.

Dat betekent dat, terwijl je bijvoorbeeld een instantie kunt maken voor [a] je geen instantie kunt maken voor specifiek [Int] .; FlexibleInstances ontspant dat:

class C a where

-- works out of the box
instance C [a] where

-- requires FlexibleInstances
instance C [Int] where

OverloadedStrings

Normaal hebben stringliterals in Haskell een type String (wat een type-alias is voor [Char] ). Hoewel dit geen probleem is voor kleinere educatieve programma's, vereisen real-world applicaties vaak een efficiëntere opslag, zoals Text of ByteString .

OverloadedStrings verandert eenvoudig het type literals in

"test" :: Data.String.IsString a => a

Zodat ze direct kunnen worden doorgegeven aan functies die een dergelijk type verwachten. Veel bibliotheken implementeren deze interface voor hun string-achtige typen, waaronder Data.Text en Data.ByteString die beide bepaalde tijd- en ruimtevoordelen bieden ten opzichte van [Char] .

Er zijn ook enkele unieke toepassingen van OverloadedStrings zoals die uit de eenvoudige bibliotheek van Postgresql waarmee SQL-query's in dubbele aanhalingstekens kunnen worden geschreven, zoals een normale string, maar biedt bescherming tegen onjuiste aaneenschakeling, een beruchte bron van SQL-injectieaanvallen.

Om een instantie van de klasse IsString , moet u de fromString implementeren. Voorbeeld :

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" ]

Dit voorbeeld met dank aan Lyndon Maydwell ( sordina op GitHub) hier gevonden.

TupleSections

Een syntactische extensie waarmee de tuple-constructor (een operator) kan worden toegepast op een sectie-manier:

(a,b) == (,) a b

-- With TupleSections
(a,b) == (,) a b == (a,) b == (,b) a

N-tupels

Het werkt ook voor tupels met een arity groter dan twee

(,2,) 1 3 == (1,2,3)

In kaart brengen

Dit kan handig zijn op andere plaatsen waar secties worden gebruikt:

map (,"tag") [1,2,3] == [(1,"tag"), (2, "tag"), (3, "tag")]

Het bovenstaande voorbeeld zonder deze extensie zou er als volgt uitzien:

map (\a -> (a, "tag")) [1,2,3]

UnicodeSyntax

Een extensie waarmee u Unicode-tekens kunt gebruiken in plaats van bepaalde ingebouwde operatoren en namen.

ASCII Unicode Toepassingen)
:: heeft type
-> functietypen, lambda, case takken, etc.
=> klassenbeperkingen
forall expliciet polymorfisme
<- do notatie
* het type (of soort) van typen (bijv. Int :: ★ )
>- proc notatie voor Arrows
-< proc notatie voor Arrows
>>- proc notatie voor Arrows
-<< proc notatie voor Arrows

Bijvoorbeeld:

runST :: (forall s. ST s a) -> a

zou worden

runST ∷ (∀ s. ST s a) → a

Merk op dat het voorbeeld van * versus enigszins verschilt: omdat * niet gereserveerd is, werkt ook op dezelfde manier als * voor vermenigvuldiging, of een andere functie met de naam (*) , en omgekeerd. Bijvoorbeeld:

ghci> 2 ★ 3
6
ghci> let (*) = (+) in 2 ★ 3
5
ghci> let (★) = (-) in 2 * 3
-1

BinaryLiterals

Met Standaard Haskell kunt u gehele letterlijke getallen schrijven in decimaal (zonder voorvoegsel), hexadecimaal (voorafgegaan door 0x of 0X ) en octaal (voorafgegaan door 0o of 0O ). De extensie BinaryLiterals voegt de optie binair toe (voorafgegaan door 0b of 0B ).

0b1111 == 15     -- evaluates to: True

ExistentialQuantification

Dit is een type systeemuitbreiding waarmee types kunnen worden gebruikt die existentieel zijn gekwantificeerd, of met andere woorden, typevariabelen hebben die alleen tijdens runtime worden geïnstantieerd .

Een waarde van het existentiële type is vergelijkbaar met een verwijzing naar een abstract-basisklasse in OO-talen: u weet niet het exacte type in, maar u kunt de klasse van typen beperken.

data S = forall a. Show a => S a

of gelijkwaardig, met GADT-syntaxis:

{-# LANGUAGE GADTs #-}
data S where
   S :: Show a => a -> S

Existentiële types openen de deur naar dingen zoals bijna-heterogene containers: zoals hierboven gezegd, er kunnen eigenlijk verschillende typen in een S waarde zijn, maar ze kunnen allemaal worden show , dus je kunt ook

instance Show S where
    show (S a) = show a   -- we rely on (Show a) from the above

Nu kunnen we een verzameling van dergelijke objecten maken:

ss = [S 5, S "test", S 3.0]

Dat stelt ons ook in staat om het polymorfe gedrag te gebruiken:

mapM_ print ss

Existentials kunnen erg krachtig zijn, maar merk op dat ze eigenlijk niet vaak nodig zijn in Haskell. In het bovenstaande voorbeeld is het enige dat u met de instantie Show kunt doen, de waarden weergeven (duh!), Dwz een stringvoorstelling maken. Het hele S type bevat daarom exact evenveel informatie als de tekenreeks die u krijgt bij het weergeven. Daarom is het meestal beter om die string gewoon meteen op te slaan, vooral omdat Haskell lui is en daarom de string in eerste instantie in elk geval alleen een niet-beoordeelde thunk zal zijn.

Aan de andere kant veroorzaken existentialiteiten enkele unieke problemen. Bijvoorbeeld, de manier waarop de type-informatie is 'verborgen' in een existentieel. Als u een patroon-match maakt op een S waarde, hebt u het ingesloten type in bereik (meer precies, de Show instantie), maar deze informatie kan nooit aan het bereik ontsnappen, wat daarom een beetje een "geheime genootschap" wordt: de compiler laat niets aan het bereik ontsnappen, behalve waarden waarvan het type al van buiten bekend is. Dit kan leiden tot vreemde fouten zoals Couldn't match type 'a0' with '()' 'a0' is untouchable .


Vergelijk dit met het gewone parametrische polymorfisme, dat over het algemeen tijdens het compileren wordt opgelost (waardoor het type volledig kan worden gewist).


Bestaande typen verschillen van Rank-N-typen - deze uitbreidingen zijn grofweg dubbel van elkaar: om waarden van een existentieel type daadwerkelijk te gebruiken, hebt u een (mogelijk beperkte) polymorfe functie nodig, zoals in het voorbeeld wordt show . Een polymorfe functie is universeel gekwantificeerd, dat wil zeggen dat het voor elk type in een bepaalde klasse werkt, terwijl existentiële kwantificatie betekent dat het voor een bepaald type werkt dat a priori onbekend is. Als je een polymorfe functie hebt, is dat voldoende, maar om polymorfe functies als argumenten door te geven, heb je {-# LANGUAGE Rank2Types #-} :

genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)

LambdaCase

Een syntactische extensie waarmee u \case kunt schrijven in plaats van \arg -> case arg of .

Overweeg de volgende functiedefinitie:

dayOfTheWeek :: Int -> String
dayOfTheWeek 0 = "Sunday"
dayOfTheWeek 1 = "Monday"
dayOfTheWeek 2 = "Tuesday"
dayOfTheWeek 3 = "Wednesday"
dayOfTheWeek 4 = "Thursday"
dayOfTheWeek 5 = "Friday"
dayOfTheWeek 6 = "Saturday"

Als u wilt voorkomen dat de functienaam wordt herhaald, kunt u zoiets schrijven als:

dayOfTheWeek :: Int -> String
dayOfTheWeek i = case i of
    0 -> "Sunday"
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"

Met de extensie LambdaCase kunt u dat als een functie-uitdrukking schrijven, zonder het argument te hoeven noemen:

{-# LANGUAGE LambdaCase #-}

dayOfTheWeek :: Int -> String
dayOfTheWeek = \case
    0 -> "Sunday"
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"

RankNTypes

Stel je de volgende situatie voor:

foo :: Show a => (a -> String) -> String -> Int -> IO ()
foo show' string int = do
   putStrLn (show' string)
   putStrLn (show' int)

Hier willen we een functie doorgeven die een waarde omzet in een String, die functie toepassen op zowel een stringparameter als de parameter int en ze beide afdrukken. Naar mijn mening is er geen reden waarom dit zou mislukken! We hebben een functie die werkt op beide typen parameters die we doorgeven.

Helaas zal dit geen controle typen! GHC leidt het a soort gebaseerd van de eerste vermelding in de functie lichaam. Dat wil zeggen, zodra we slaan:

putStrLn (show' string)

GHC zal die show' :: String -> String afleiden show' :: String -> String , omdat string een String . Het zal opblazen terwijl het probeert het te show' int .

RankNTypes kunt u in plaats daarvan de typeaanduiding als volgt schrijven en alle functies kwantificeren die voldoen aan het type van de show' :

foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()

Dit is rang 2 polymorfisme: We zijn te beweren dat de show' functie moet werk voor iedereen a s binnen onze functie, en de vorige uitvoering werkt nu.

De extensie RankNTypes maakt willekeurig nesten van forall ... blokken in type handtekeningen mogelijk. Met andere woorden, het maakt polymorfisme van rang N mogelijk.

OverloadedLists

toegevoegd in GHC 7.8 .

OverloadedLists, vergelijkbaar met OverloadedStrings , laat toe om lijstliteralen als volgt te ontcijferen:

[]          -- fromListN 0 []
[x]         -- fromListN 1 (x : [])
[x .. ]     -- fromList (enumFrom x)

Dit is handig bij het omgaan met typen zoals Set , Vector en Map s.

['0' .. '9']             :: Set Char
[1 .. 10]                :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z']             :: Text

IsList klasse in GHC.Exts is bedoeld voor gebruik met deze extensie.

IsList is uitgerust met één type functie, Item en drie functies, fromList :: [Item l] -> l , toList :: l -> [Item l] en fromListN :: Int -> [Item l] -> l waar fromListN is optioneel. Typische implementaties zijn:

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

Voorbeelden uit OverloadedLists - GHC .

FunctionalDependencies

Als u een type-klasse met meerdere parameters heeft met argumenten a, b, c en x, kunt u met deze extensie uitdrukken dat het type x uniek kan worden geïdentificeerd uit a, b en c:

class SomeClass a b c x | a b c -> x where ...

Wanneer een instantie van een dergelijke klasse wordt gedeclareerd, wordt deze vergeleken met alle andere instanties om ervoor te zorgen dat de functionele afhankelijkheid geldt, dat wil zeggen dat er geen andere instantie met dezelfde abc maar een andere x bestaat.

U kunt meerdere afhankelijkheden opgeven in een door komma's gescheiden lijst:

class OtherClass a b c d | a b -> c d, a d -> b where ...

In MTL kunnen we bijvoorbeeld zien:

class MonadReader r m| m -> r where ...
instance MonadReader r ((->) r) where ...

Als u nu een waarde van het type MonadReader a ((->) Foo) => a , kan de compiler die a ~ Foo afleiden, omdat het tweede argument de eerste volledig bepaalt en het type dienovereenkomstig zal vereenvoudigen.

De klasse SomeClass kan worden beschouwd als een functie van de argumenten abc die resulteert in x . Dergelijke klassen kunnen worden gebruikt om berekeningen in het typesysteem uit te voeren.

GADTs

Conventionele algebraïsche gegevenstypen zijn parametrisch in hun typevariabelen. Als we bijvoorbeeld een ADT zoals definiëren

data Expr a = IntLit Int 
            | BoolLit Bool 
            | If (Expr Bool) (Expr a) (Expr a)

in de hoop dat dit statisch niet-goed getypeerde voorwaardes zal uitsluiten, zal dit zich niet gedragen zoals verwacht, aangezien het type IntLit :: Int -> Expr a wordt gekwantificeerd: voor elke keuze van a , produceert het een waarde van type Expr a . In het bijzonder hebben we voor a ~ Bool IntLit :: Int -> Expr Bool , waardoor we zoiets als If (IntLit 1) e1 e2 construeren, wat het type van de If constructor probeerde uit te sluiten.

Gegeneraliseerde algebraïsche gegevenstypen stellen ons in staat om het resulterende type van een gegevensconstructor te besturen, zodat deze niet alleen parametrisch zijn. We kunnen ons Expr type als een GADT als volgt herschrijven:

data Expr a where
  IntLit :: Int -> Expr Int
  BoolLit :: Bool -> Expr Bool
  If :: Expr Bool -> Expr a -> Expr a -> Expr a

Hier is het type van de constructor IntLit Int -> Expr Int , en dus zal IntLit 1 :: Expr Bool niet tikken.

Patroonovereenkomst op een GADT-waarde veroorzaakt verfijning van het type geretourneerde term. Het is bijvoorbeeld mogelijk om een evaluator voor Expr a te schrijven zoals:

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 

Merk op dat we in de bovenstaande definities (+) kunnen gebruiken, omdat wanneer bijvoorbeeld IntLit x overeenkomt met een patroon, we ook leren dat a ~ Int (en ook voor not en if_then_else_ wanneer a ~ Bool ).

ScopedTypeVariables

ScopedTypeVariables kunt u verwijzen naar universeel gekwantificeerde typen binnen een aangifte. Om meer expliciet te zijn:

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)

Het belangrijkste is dat we kunnen gebruiken a , b en c te instrueren de compiler in subexpressies van de verklaring (de tupel in de where clausule en de eerste a in het uiteindelijke resultaat). In de praktijk helpt ScopedTypeVariables bij het schrijven van complexe functies als een som van delen, waardoor de programmeur typeaanduidingen kan toevoegen aan tussenliggende waarden die geen concrete typen hebben.

PatternSynonyms

Patroonsynoniemen zijn abstracties van patronen vergelijkbaar met hoe functies abstracties van uitdrukkingen zijn.

Laten we voor dit voorbeeld kijken naar de interface Data.Sequence onthult, en laten we kijken hoe het kan worden verbeterd met patroonsynoniemen. Het Seq type is een gegevenstype dat intern een gecompliceerde weergave gebruikt om een goede asymptotische complexiteit te bereiken voor verschillende bewerkingen, met name zowel O (1) (on) consing en (un) snocing.

Maar deze weergave is log en sommige van zijn invarianten kunnen niet worden uitgedrukt in het type systeem van Haskell. Vanwege dit wordt het Seq type aan gebruikers als een abstract type blootgesteld, samen met invariant-conserverende accessor- en constructorfuncties, waaronder:

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

Maar het gebruik van deze interface kan een beetje omslachtig zijn:

uncons :: Seq a -> Maybe (a, Seq a)
uncons xs = case viewl xs of
    x :< xs' -> Just (x, xs')
    EmptyL -> Nothing

We kunnen weergavepatronen gebruiken om het enigszins op te ruimen:

{-# LANGUAGE ViewPatterns #-}

uncons :: Seq a -> Maybe (a, Seq a)
uncons (viewl -> x :< xs) = Just (x, xs)
uncons _ = Nothing

Met behulp van de PatternSynonyms kunnen we een nog mooiere interface bieden door patroonvergelijking te laten doen alsof we een tegens- of een snoc-lijst hebben:

{-# 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)

Dit stelt ons in staat om uncons in een zeer natuurlijke stijl te schrijven:

uncons :: Seq a -> Maybe (a, Seq a)
uncons (x :< xs) = Just (x, xs)
uncons _ = Nothing

RecordWildCards

Zie RecordWildCards



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow