Ricerca…


Osservazioni

Queste estensioni di lingua sono in genere disponibili quando si utilizza il Glasgow Haskell Compiler (GHC) poiché non fanno parte del rapporto della lingua Haskell 2010 approvato. Per utilizzare queste estensioni, è necessario informare il compilatore utilizzando un flag o posizionare un programma LANGUAGE prima della parola chiave module in un file. La documentazione ufficiale è disponibile nella sezione 7 della guida utenti GCH.

Il formato del programma LANGUAGE è {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-} . Questo è il letterale {-# seguito da LANGUAGE seguito da un elenco di estensioni separate da virgole e infine da #-} chiusura. È possibile inserire più programmi LANGUAGE in un unico file.

MultiParamTypeClasses

È un'estensione molto comune che consente classi di tipi con più parametri di tipo. Puoi pensare a MPTC come una relazione tra i tipi.

{-# LANGUAGE MultiParamTypeClasses #-}

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

instance Convertable Int Float where
    convert i = fromIntegral i

L'ordine dei parametri è importante.

Gli MPTC possono a volte essere sostituiti con famiglie di tipi.

FlexibleInstances

Le istanze regolari richiedono:

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.

Ciò significa che, ad esempio, mentre è possibile creare un'istanza per [a] non è possibile creare un'istanza specifica per [Int] .; FlexibleInstances rilassa quello:

class C a where

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

-- requires FlexibleInstances
instance C [Int] where

OverloadedStrings

Normalmente, i valori letterali di stringa in Haskell hanno un tipo di String (che è un alias di tipo per [Char] ). Mentre questo non è un problema per i programmi educativi più piccoli, le applicazioni del mondo reale richiedono spesso una memoria più efficiente come Text o ByteString .

OverloadedStrings semplicemente cambia il tipo di letterali in

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

Consentendo loro di passare direttamente alle funzioni che si aspettano tale tipo. Molte librerie implementano questa interfaccia per i loro tipi simili a stringhe, tra cui Data.Text e Data.ByteString, che offrono entrambi alcuni vantaggi in termini di tempo e spazio rispetto a [Char] .

Ci sono anche alcuni usi unici di OverloadedStrings come quelli della libreria semplice di Postgresql che consente di scrivere query SQL in virgolette doppie come una normale stringa, ma fornisce protezioni contro la concatenazione impropria, una famigerata fonte di attacchi SQL injection.

Per creare un'istanza della classe IsString è necessario eseguire l' fromString funzione fromString . Esempio :

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

Questo esempio per gentile concessione di Lyndon Maydwell ( sordina su GitHub) trovato qui .

TupleSections

Un'estensione sintattica che consente di applicare il costruttore tuple (che è un operatore) in una sezione:

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

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

N-tuple

Funziona anche per le tuple con un'arità maggiore di due

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

Mappatura

Questo può essere utile in altri luoghi in cui vengono utilizzate le sezioni:

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

L'esempio precedente senza questa estensione sarebbe simile a questo:

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

UnicodeSyntax

Un'estensione che consente di utilizzare caratteri Unicode al posto di determinati operatori e nomi incorporati.

ASCII Unicode Utilizzare (s)
:: ha tipo
-> tipi di funzioni, lambda, case derivati, ecc.
=> vincoli di classe
forall polimorfismo esplicito
<- do notazione
* il tipo (o il tipo) di tipi (es. Int :: ★ )
>- notazione proc per Arrows
-< notazione proc per Arrows
>>- notazione proc per Arrows
-<< notazione proc per Arrows

Per esempio:

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

potrebbe diventare

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

Nota che l'esempio * vs. è leggermente diverso: poiché * non è riservato, funziona allo stesso modo di * per la moltiplicazione, o qualsiasi altra funzione chiamata (*) , e viceversa. Per esempio:

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

BinaryLiterals

Haskell standard consente di scrivere valori letterali interi decimali (senza alcun prefisso), esadecimali (preceduti da 0x o 0X ) e ottali (preceduti da 0o o 0O ). L'estensione BinaryLiterals aggiunge l'opzione di binario (preceduta da 0b o 0B ).

0b1111 == 15     -- evaluates to: True

ExistentialQuantification

Si tratta di un'estensione di sistema di tipo che consente tipi quantificati in modo esistenziale o, in altre parole, di variabili di tipo che vengono solo istanziate in fase di esecuzione .

Un valore di tipo esistenziale è simile a un riferimento alla classe base astratta nei linguaggi OO: non si conosce il tipo esatto in contiene, ma è possibile vincolare la classe di tipi.

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

o in modo equivalente, con la sintassi GADT:

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

I tipi esistenziali aprono la porta a cose come contenitori quasi eterogenei: come detto sopra, possono esserci vari tipi in un valore S , ma tutti possono essere show , quindi puoi anche fare

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

Ora possiamo creare una collezione di tali oggetti:

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

Il che ci consente anche di usare il comportamento polimorfico:

mapM_ print ss

Gli esistenziali possono essere molto potenti, ma si noti che in realtà non sono necessari molto spesso in Haskell. Nell'esempio sopra, tutto ciò che puoi effettivamente fare con l'istanza Show è mostrare (duh!) I valori, ovvero creare una rappresentazione stringa. L'intero tipo S quindi contiene esattamente quante informazioni come la stringa che si ottiene quando lo si mostra. Pertanto, di solito è meglio memorizzare semplicemente quella stringa immediatamente, specialmente dal momento che Haskell è pigro e quindi la stringa inizialmente sarà solo un thunk non valutato comunque.

D'altra parte, gli esistenziali causano alcuni problemi unici. Ad esempio, il modo in cui le informazioni sul tipo sono "nascoste" in un campo esistenziale. Se si esegue il pattern-match su un valore S , si avrà il tipo contenuto in scope (più precisamente la sua istanza Show ), ma questa informazione non può mai sfuggire al suo scope, che diventa quindi un po 'una "società segreta": il compilatore non lascia che qualcosa sfugga all'ambito tranne valori il cui tipo è già noto dall'esterno. Questo può portare a strani errori come Couldn't match type 'a0' with '()' 'a0' is untouchable .


Confrontalo con il normale polimorfismo parametrico, che generalmente viene risolto in fase di compilazione (consentendo la cancellazione completa del tipo).


I tipi esistenziali sono diversi dai tipi Rank-N - queste estensioni sono, in parole povere, duali l'una all'altra: per utilizzare effettivamente i valori di un tipo esistenziale, è necessaria una funzione (possibilmente vincolata) polimorfica, come show nell'esempio. Una funzione polimorfica è universalmente quantificata, cioè funziona per qualsiasi tipo in una determinata classe, mentre la quantificazione esistenziale significa che funziona per un tipo particolare che è a priori sconosciuto. Se hai una funzione polimorfica, è sufficiente, tuttavia per passare le funzioni polimorfiche come argomenti, hai bisogno di {-# LANGUAGE Rank2Types #-} :

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

LambdaCase

Un'estensione sintattica che consente di scrivere \case al posto di \arg -> case arg of .

Considera la seguente definizione di funzione:

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

Se vuoi evitare di ripetere il nome della funzione, potresti scrivere qualcosa come:

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

Usando l'estensione LambdaCase, puoi scriverlo come un'espressione di funzione, senza dover denominare l'argomento:

{-# LANGUAGE LambdaCase #-}

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

RankNTypes

Immagina la seguente situazione:

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

Qui, vogliamo passare una funzione che converte un valore in una stringa, applicare quella funzione sia al parametro stringa che al parametro int e stamparli entrambi. Nella mia mente, non c'è motivo per cui questo dovrebbe fallire! Abbiamo una funzione che funziona su entrambi i tipi di parametri che stiamo passando.

Sfortunatamente, questo non scriverà check! GHC deduce a tipo basato sulla sua prima occorrenza nel corpo della funzione. Cioè, non appena abbiamo colpito:

putStrLn (show' string)

GHC dedurrà quello show' :: String -> String , poiché string è una String . Continuerà a saltare in aria mentre prova a show' int .

RankNTypes ti consente invece di scrivere la firma del tipo come segue, quantificando su tutte le funzioni che soddisfano il tipo di show' :

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

Si tratta di rango 2 polimorfismo: stiamo affermando che la show' funzione deve lavorare per tutti a s all'interno della nostra funzione e l'attuazione precedente ora funziona.

L'estensione RankNTypes consente l'annidamento arbitrario di tutti i forall ... blocchi nelle firme dei tipi. In altre parole, consente il polimorfismo di grado N.

OverloadedLists

aggiunto in GHC 7.8 .

OverloadedLists, simile a OverloadedStrings , consente di convertire i letterali delle liste come segue:

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

Questo è utile quando si tratta di tipi come Set , Vector e Map s.

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

IsList classe IsList in GHC.Exts deve essere utilizzata con questa estensione.

IsList è dotato di una funzione tipo, Item e tre funzioni, da fromList :: [Item l] -> l , a toList :: l -> [Item l] e fromListN :: Int -> [Item l] -> l dove fromListN è facoltativo. Le implementazioni tipiche sono:

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

Esempi tratti da OverloadedLists - GHC .

FunctionalDependencies

Se si dispone di una classe di tipo a più parametri con argomenti a, b, c e x, questa estensione consente di esprimere che il tipo x può essere identificato in modo univoco da a, b, e c:

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

Quando si dichiara un'istanza di tale classe, verrà verificata rispetto a tutte le altre istanze per assicurarsi che la dipendenza funzionale sia valida, cioè non esiste un'altra istanza con lo stesso abc ma esiste una x differente.

È possibile specificare più dipendenze in un elenco separato da virgole:

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

Ad esempio in MTL possiamo vedere:

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

Ora, se hai un valore di tipo MonadReader a ((->) Foo) => a , il compilatore può dedurre che a ~ Foo , poiché il secondo argomento determina completamente il primo e semplificherà il tipo di conseguenza.

La classe SomeClass può essere pensata come una funzione degli argomenti abc che risulta in x . Tali classi possono essere utilizzate per eseguire calcoli nel sistema dei tipi.

GADTs

I tipi di dati algebrici convenzionali sono parametrici nelle loro variabili di tipo. Ad esempio, se definiamo un ADT come

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

con la speranza che questo eliminerà staticamente condizionali non ben digitati, questo non si comporterà come previsto poiché il tipo di IntLit :: Int -> Expr a è IntLit :: Int -> Expr a quantificato: per qualsiasi scelta di a , produce un valore di tipo Expr a . In particolare, per a ~ Bool , abbiamo IntLit :: Int -> Expr Bool , che ci consente di costruire qualcosa come If (IntLit 1) e1 e2 che è ciò che il tipo di costruttore di If stava cercando di escludere.

I tipi di dati algebrici generalizzati ci consentono di controllare il tipo risultante di un costruttore di dati in modo che non siano semplicemente parametrici. Possiamo riscrivere il nostro tipo Expr come GADT in questo modo:

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

Qui, il tipo del costruttore IntLit è Int -> Expr Int , e quindi IntLit 1 :: Expr Bool non digiterà tipograficamente.

La corrispondenza del modello su un valore GADT causa il perfezionamento del tipo di termine restituito. Ad esempio, è possibile scrivere un valutatore per Expr a questo modo:

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 

Si noti che siamo in grado di usare (+) nelle definizioni precedenti perché quando ad esempio IntLit x corrisponde al modello, impariamo anche che a ~ Int (e allo stesso modo per not e if_then_else_ quando a ~ Bool ).

ScopedTypeVariables

ScopedTypeVariables ti consente di fare riferimento a tipi universalmente quantificati all'interno di una dichiarazione. Per essere più espliciti:

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)

La cosa importante è che siamo in grado di utilizzare a , b e c per istruire il compilatore in sottoespressioni della dichiarazione (la tupla in where clausola e la prima a nel risultato finale). In pratica, ScopedTypeVariables aiuta a scrivere funzioni complesse come una somma di parti, consentendo al programmatore di aggiungere firme di tipo a valori intermedi che non hanno tipi concreti.

PatternSynonyms

I sinonimi di pattern sono astrazioni di pattern simili a come le funzioni sono astrazioni di espressioni.

Per questo esempio, esaminiamo l'interfaccia che Data.Sequence espone e vediamo come può essere migliorato con i sinonimi di pattern. Il tipo Seq è un tipo di dati che, internamente, utilizza una rappresentazione complicata per ottenere una buona complessità asintotica per varie operazioni, in particolare O (1) (un) consing e (un) snocing.

Ma questa rappresentazione è ingombrante e alcuni dei suoi invarianti non possono essere espressi nel sistema di tipi di Haskell. Per questo Seq , il tipo Seq viene esposto agli utenti come un tipo astratto, insieme a funzioni di accesso e di costruzione che preservano invariabili, tra cui:

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

Ma usare questa interfaccia può essere un po 'macchinoso:

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

Possiamo usare i modelli di visualizzazione per ripulirlo un po ':

{-# LANGUAGE ViewPatterns #-}

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

Usando l'estensione del linguaggio PatternSynonyms , possiamo dare un'interfaccia ancora più carina, consentendo la corrispondenza dei pattern per far finta di avere una lista cons o snoc:

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

Questo ci permette di scrivere uncons in uno stile molto naturale:

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

RecordWildCards

Vedi RecordWildCards



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow