Sök…


Anmärkningar

Dessa språkförlängningar är vanligtvis tillgängliga när du använder Glasgow Haskell Compiler (GHC) eftersom de inte ingår i den godkända Haskell 2010-språkrapporten . För att använda dessa tillägg måste man antingen informera kompilatorn med hjälp av en flagga eller placera ett LANGUAGE program före module nyckelord i en fil. Den officiella dokumentationen finns i avsnitt 7 i GCH-användarguiden.

Formatet för LANGUAGE programmet är {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-} . Det är den bokstavliga {-# följt av LANGUAGE följt av en kommaseparerad lista med tillägg och slutligen slutningen #-} . Flera LANGUAGE program kan placeras i en fil.

MultiParamTypeClasses

Det är en mycket vanlig tillägg som tillåter typklasser med flera typparametrar. Du kan tänka på MPTC som en relation mellan typer.

{-# LANGUAGE MultiParamTypeClasses #-}

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

instance Convertable Int Float where
    convert i = fromIntegral i

Parternas ordning är viktig.

MPTC kan ibland ersättas med typfamiljer.

FlexibleInstances

Regelbundna instanser kräver:

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.

Det betyder att du till exempel inte kan skapa en instans för specifikt [Int] medan du kan skapa en instans för [a] ; FlexibleInstances slappnar av att:

class C a where

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

-- requires FlexibleInstances
instance C [Int] where

OverloadedStrings

Normalt har strängbokstäver i Haskell en typ av String (som är ett typalias för [Char] ). Även om detta inte är ett problem för mindre utbildningsprogram, kräver verkliga applikationer ofta mer effektiv lagring, t.ex. Text eller ByteString .

OverloadedStrings ändrar helt enkelt bokstäverna till

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

Tillåter dem att direkt överföras till funktioner som förväntar sig en sådan typ. Många bibliotek implementerar detta gränssnitt för sina strängliknande typer inklusive Data.Text och Data.ByteString som både ger vissa tids- och utrymme fördelar jämfört med [Char] .

Det finns också några unika användningar av OverloadedStrings som de från det Postgresql-enkla biblioteket som gör det möjligt att skriva SQL-frågor i dubbla citat som en vanlig sträng, men ger skydd mot felaktig sammanlänkning, en beryktad källa för SQL-injektionsattacker.

För att skapa en instans av klassen IsString måste du implementera fromString funktionen. Exempel :

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

Detta exempel med tillstånd av Lyndon Maydwell ( sordina på GitHub) hittades här .

TupleSections

En syntaktisk förlängning som gör det möjligt att applicera tupelkonstruktorn (som är en operatör) på ett avsnitt:

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

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

N-tupler

Det fungerar också för tupler med mer än två arity

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

kartläggning

Detta kan vara användbart på andra platser där avsnitt används:

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

Exemplet ovan utan denna tillägg skulle se ut så här:

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

UnicodeSyntax

Ett tillägg som låter dig använda Unicode-tecken istället för vissa inbyggda operatörer och namn.

ASCII Unicode Användning (ar)
:: har typ
-> funktionstyper, lambdas, case grenar, etc.
=> klass begränsningar
forall uttrycklig polymorfism
<- do notation
* typ (eller typ) av typer (t.ex. Int :: ★ )
>- proc notation för Arrows
-< proc notation för Arrows
>>- proc notation för Arrows
-<< proc notation för Arrows

Till exempel:

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

skulle bli

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

Observera att exemplet * vs. är något annorlunda: eftersom * inte är reserverat, fungerar också på samma sätt som * för multiplikation, eller någon annan funktion som heter (*) , och vice versa. Till exempel:

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

BinaryLiterals

Standard Haskell låter dig skriva heltalslitteraler i decimal (utan något prefix), hexadecimal (föregås av 0x eller 0X ) och oktal (föregås av 0o eller 0O ). BinaryLiterals tillägget lägger till alternativet för binär (föregås av 0b eller 0B ).

0b1111 == 15     -- evaluates to: True

ExistentialQuantification

Det här är ett typsystemförlängning som tillåter typer som är existentiellt kvantifierade, eller med andra ord har typvariabler som bara instanseras vid körning .

Värdet existentiell typ liknar en abstrakt-base-klass referens i OO språk: du vet inte den exakta typen i innehåller, men du kan begränsa klassen typer.

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

eller motsvarande, med GADT-syntax:

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

Existentiella typer öppnar dörren för saker som nästan heterogena behållare: som sagt ovan kan det faktiskt finnas olika typer i ett S värde, men alla kan vara show n, därmed kan du också göra

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

Nu kan vi skapa en samling av sådana objekt:

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

Som också tillåter oss att använda det polymorfa beteendet:

mapM_ print ss

Existentials kan vara mycket kraftfulla, men observera att de faktiskt inte är nödvändiga så ofta i Haskell. I exemplet ovan, allt du faktiskt kan göra med Show instansen är att visa (duh!) Värdena, dvs. skapa en strängrepresentation. Hela S typen innehåller därför exakt lika mycket information som strängen du får när du visar den. Därför är det vanligtvis bättre att helt enkelt lagra den strängen direkt, speciellt eftersom Haskell är lat och därför kommer strängen till en början bara vara en obevärderad thunk ändå.

Å andra sidan orsakar existensiella problem unika problem. Till exempel hur typinformationen är "dold" i en existensiell. Om du mönstermatchar på ett S värde, har du den innehöll typen i omfattning (mer exakt dess Show instans), men denna information kan aldrig undgå sin omfattning, som därför blir lite av ett "hemligt samhälle": kompilatorn låter inget undkomma omfattningen utom värden vars typ redan är känd från utsidan. Detta kan leda till konstiga fel som inte Couldn't match type 'a0' with '()' 'a0' is untouchable .


Kontrastera detta med vanlig parametrisk polymorfism, som generellt löses vid sammanställningstiden (tillåter radering av full typ).


Existentiella typer skiljer sig från Rank-N-typer - dessa tillägg är grovt sett dubbla mot varandra: för att faktiskt använda värden av en existentiell typ behöver du en (eventuellt begränsad-) polymorf funktion, som show i exemplet. En polymorf funktion är universellt kvantifierad, det vill säga den fungerar för alla typer i en given klass, medan existentiell kvantifiering innebär att den fungerar för någon viss typ som är priori okänd. Om du har en polymorfisk funktion är det tillräckligt, men för att klara polymorfa funktioner som sådana, behöver du {-# LANGUAGE Rank2Types #-} :

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

LambdaCase

En syntaktisk förlängning som låter dig skriva \case i stället för \arg -> case arg of .

Tänk på följande 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"

Om du vill undvika att upprepa funktionsnamnet kan du skriva något som:

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

Med hjälp av LambdaCase-förlängningen kan du skriva det som ett funktionsuttryck utan att behöva namnge argumentet:

{-# LANGUAGE LambdaCase #-}

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

RankNTypes

Föreställ dig följande situation:

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

Här vill vi överföra en funktion som konverterar ett värde till en sträng, tillämpa den funktionen på både en strängparameter och en int-parameter och skriva ut båda. Enligt min mening finns det ingen anledning att detta skulle misslyckas! Vi har en funktion som fungerar på båda typerna av parametrar som vi skickar in.

Tyvärr skriver det inte check! GHC tillhandahåller a typ baserad på den första händelsen i funktionskroppen. Det är, så snart vi träffade:

putStrLn (show' string)

GHC kommer att sluta sig till att show' :: String -> String , eftersom string är en String . Det fortsätter att spränga medan du försöker show' int .

RankNTypes låter dig istället skriva RankNTypes sätt, kvantifiera över alla funktioner som uppfyller show' :

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

Detta är rank 2 polymorfism: Vi hävdar att show' funktionen måste fungera för alla a s inom vår funktion, och den tidigare genomförande fungerar nu.

RankNTypes tillåter godtycklig häckning av forall ... block i typsignaturer. Med andra ord tillåter det rang N-polymorfism.

OverloadedLists

tillsatt i GHC 7.8 .

Överbelastade listor, liknar OverloadedStrings , gör det möjligt att desugareda listlitteraler enligt följande:

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

Detta är praktiskt när man hanterar typer som Set , Vector och Map s.

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

IsList klassen i GHC.Exts är avsedd att användas med den här utvidgningen.

IsList är utrustad med en typfunktion, Item och tre funktioner, fromList :: [Item l] -> l , toList :: l -> [Item l] och fromListN :: Int -> [Item l] -> l där fromListN är valfritt. Typiska implementeringar är:

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

Exempel tagna från OverloadedLists - GHC .

FunctionalDependencies

Om du har en typklass med flera parametrar med argumenten a, b, c och x låter det här tillägget uttrycka att typen x kan identifieras unikt från a, b och c:

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

När man förklarar en instans av en sådan klass kommer det att kontrolleras mot alla andra instanser för att se till att det funktionella beroendet rymmer, det vill säga ingen annan instans med samma abc men olika x finns.

Du kan ange flera beroenden i en kommaseparerad lista:

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

I MTL kan vi till exempel se:

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

Om du har ett värde av typen MonadReader a ((->) Foo) => a kan kompilatorn dra slutsatsen att a ~ Foo , eftersom det andra argumentet bestämmer det första och kommer att förenkla typen därefter.

SomeClass kan ses som en funktion av argumenten abc som resulterar i x . Sådana klasser kan användas för att göra beräkningar i typsystemet.

GADTs

Konventionella algebraiska datatyper är parametriska i sina typvariabler. Om vi till exempel definierar en ADT som

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

med hopp om att denna statiskt kommer att utesluta icke-väl skrivit villkors, kommer detta inte beter sig som förväntat eftersom den typ av IntLit :: Int -> Expr a är universially kvantifieras: för något val av a , ger det ett värde av typen Expr a Speciellt för a ~ Bool har vi IntLit :: Int -> Expr Bool , vilket gör att vi kan konstruera något som If (IntLit 1) e1 e2 vilket är vad typen av konstruktören If försökte utesluta.

Generaliserade algebraiska datatyper tillåter oss att kontrollera den resulterande typen av en datakonstruktör så att de inte bara är parametriska. Vi kan skriva om vår Expr typ som en GADT så här:

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

Här är typen av konstruktören IntLit Int -> Expr Int , och så kommer IntLit 1 :: Expr Bool inte att kontrollera.

Mönstermatchning på ett GADT-värde orsakar förfining av typen av returnerad term. Till exempel är det möjligt att skriva en utvärderare för Expr a som denna:

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 

Observera att vi kan använda (+) i ovanstående definitioner eftersom när t.ex. IntLit x är mönstermatchat, lär vi oss också att a ~ Int (och likaså för not och if_then_else_ när a ~ Bool ).

ScopedTypeVariables

ScopedTypeVariables du hänvisa till universellt kvantifierade typer i en deklaration. För att vara mer tydlig:

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)

Det viktiga är att vi kan använda a , b och c att instruera kompilatorn i underuttryck av deklarationen (tupeln i where klausulen och den första a i det slutliga resultatet). I praktiken hjälper ScopedTypeVariables att skriva komplexa funktioner som en summa av delar, vilket gör att programmeraren kan lägga till typsignaturer till mellanvärden som inte har konkreta typer.

PatternSynonyms

Mönstersynonymer är abstraktioner av mönster som liknar hur funktioner är abstraktioner av uttryck.

För detta exempel, låt oss titta på gränssnittet Data.Sequence exponerar, och låt oss se hur det kan förbättras med mönstersynonymer. Seq typen är en datatyp som internt använder en komplicerad representation för att uppnå god asymptotisk komplexitet för olika operationer, särskilt både O (1) (un) consing och (un) snocing.

Men denna representation är olämplig och några av dess invarianter kan inte uttryckas i Haskells typsystem. På grund av detta exponeras Seq typen för användare som en abstrakt typ, tillsammans med invariantbevarande accessor- och konstruktionsfunktioner, bland dem:

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

Men att använda detta gränssnitt kan vara lite besvärligt:

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

Vi kan använda visningsmönster för att rensa upp det något:

{-# LANGUAGE ViewPatterns #-}

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

Med hjälp av PatternSynonyms kan vi ge ett ännu trevligare gränssnitt genom att låta mönstermatchning låtsas att vi har en nackdel eller en snoc-lista:

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

Detta gör att vi kan skriva uncons i en mycket naturlig stil:

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

RecordWildCards

Se RecordWildCards



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow