Recherche…


Remarques

Ces extensions de langage sont généralement disponibles lors de l'utilisation du compilateur Glasgow Haskell (GHC), car elles ne font pas partie du rapport linguistique Haskell 2010 approuvé. Pour utiliser ces extensions, il faut soit informer le compilateur en utilisant un indicateur, soit placer un programme LANGUAGE avant le mot-clé du module dans un fichier. La documentation officielle se trouve dans la section 7 du guide de l'utilisateur GCH.

Le format du programme LANGUAGE est {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-} . C'est le littéral {-# suivi de LANGUAGE suivi d'une liste d'extensions séparées par des virgules, et enfin de la fermeture #-} . Plusieurs programmes LANGUAGE peuvent être placés dans un fichier.

MultiParamTypeClasses

C'est une extension très courante qui permet des classes de type avec plusieurs paramètres de type. Vous pouvez considérer MPTC comme une relation entre les types.

{-# LANGUAGE MultiParamTypeClasses #-}

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

instance Convertable Int Float where
    convert i = fromIntegral i

L'ordre des paramètres est important.

Les MPTC peuvent parfois être remplacées par des familles de type.

FlexibleInstances

Les instances régulières nécessitent:

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.

Cela signifie que, par exemple, alors que vous pouvez créer une instance pour [a] vous ne pouvez pas créer d'instance pour spécifiquement [Int] . FlexibleInstances détend que:

class C a where

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

-- requires FlexibleInstances
instance C [Int] where

Chaînes surchargées

Normalement, les littéraux de chaîne dans Haskell ont un type de String (qui est un alias de type pour [Char] ). Bien que cela ne pose pas de problème aux petits programmes éducatifs, les applications du monde réel nécessitent souvent un stockage plus efficace, tel que Text ou ByteString .

OverloadedStrings change simplement le type de littéral en

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

En leur permettant d'être directement transmis aux fonctions qui attendent un tel type. De nombreuses bibliothèques implémentent cette interface pour leurs types de type chaîne, y compris Data.Text et Data.ByteString, qui offrent tous deux des avantages en termes de temps et d'espace sur [Char] .

Il y a aussi des utilisations uniques de OverloadedStrings comme celles de la bibliothèque Postgresql-simple qui permet d'écrire des requêtes SQL entre guillemets comme une chaîne normale, mais fournit des protections contre la concaténation incorrecte, une source notoire d'attaques par injection SQL.

Pour créer une instance de la classe IsString , vous devez fromString fonction fromString . Exemple :

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

Cet exemple est une gracieuseté de Lyndon Maydwell ( sordina sur GitHub) trouvé ici .

TupleSections

Une extension syntaxique qui permet d'appliquer le constructeur de tuple (qui est un opérateur) de manière sectionnelle:

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

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

N-tuples

Il fonctionne également pour les tuples avec une arité supérieure à deux

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

Cartographie

Cela peut être utile dans d'autres endroits où des sections sont utilisées:

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

L'exemple ci-dessus sans cette extension ressemblerait à ceci:

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

UnicodeSyntax

Une extension qui vous permet d'utiliser des caractères Unicode à la place de certains opérateurs et noms intégrés.

ASCII Unicode Les usages)
:: a le type
-> types de fonctions, lambdas, branches de case , etc.
=> contraintes de classe
forall polymorphisme explicite
<- do notation
* le type (ou type) des types (par exemple, Int :: ★ )
>- proc pour les Arrows
-< proc pour les Arrows
>>- proc pour les Arrows
-<< proc pour les Arrows

Par exemple:

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

deviendrait

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

Notez que l'exemple * vs. est légèrement différent: puisque * n'est pas réservé, fonctionne de la même manière que * pour la multiplication, ou toute autre fonction nommée (*) , et vice-versa. Par exemple:

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

BinaryLiterals

Standard Haskell vous permet d'écrire des littéraux entiers en décimal (sans préfixe), hexadécimal (précédé de 0x ou 0X ) et octal (précédé de 0o ou 0O ). L'extension BinaryLiterals ajoute l'option de binary (précédé de 0b ou 0B ).

0b1111 == 15     -- evaluates to: True

ExistentialQuantification

Il s’agit d’une extension de type système qui autorise les types quantifiés de manière existentielle ou, en d’autres termes, qui ont des variables de type qui ne sont instanciées qu’à l’exécution .

Une valeur de type existentiel est similaire à une référence de classe de base abstraite dans les langages OO: vous ne connaissez pas le type exact dans contient, mais vous pouvez contraindre la classe de types.

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

ou de manière équivalente, avec la syntaxe GADT:

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

Types existentiels ouvrent la porte à des choses comme des conteneurs presque hétérogènes: comme dit plus haut, il peut effectivement être différents types dans une S valeur, mais ils peuvent tous être show n, vous pouvez donc aussi faire

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

Maintenant, nous pouvons créer une collection de tels objets:

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

Ce qui nous permet également d'utiliser le comportement polymorphe:

mapM_ print ss

Les existentiels peuvent être très puissants, mais notez qu'ils ne sont pas vraiment nécessaires dans Haskell. Dans l'exemple ci-dessus, tout ce que vous pouvez réellement faire avec l'instance Show est de montrer (duh!) Les valeurs, c.-à-d. Créer une représentation sous forme de chaîne. Le type S complet contient donc exactement autant d'informations que la chaîne que vous obtenez lorsque vous la montrez. Par conséquent, il est généralement préférable de simplement stocker cette chaîne tout de suite, d'autant plus que Haskell est paresseux et que, par conséquent, la chaîne ne sera d'abord qu'un non-évalué.

D'autre part, les existentiels causent des problèmes uniques. Par exemple, la manière dont les informations de type sont «cachées» dans un existentiel. Si vous faites correspondre un motif sur une valeur S , vous aurez le type contenu dans la portée (plus précisément son instance Show ), mais cette information ne pourra jamais échapper à sa portée, qui devient donc une «société secrète»: le compilateur ne laisse rien échapper à la portée sauf les valeurs dont le type est déjà connu de l'extérieur. Cela peut conduire à des erreurs étranges comme Couldn't match type 'a0' with '()' 'a0' is untouchable .


Comparez ceci avec le polymorphisme paramétrique ordinaire, qui est généralement résolu au moment de la compilation (permettant un effacement complet).


Les types existentiels sont différents des types Rank-N - ces extensions sont, pour ainsi dire, doubles les unes aux autres: pour utiliser réellement des valeurs de type existentiel, il faut une fonction polymorphe (éventuellement contrainte), comme show dans l'exemple. Une fonction polymorphe est universellement quantifiée, c'est-à-dire qu'elle fonctionne pour tout type dans une classe donnée, alors que la quantification existentielle signifie qu'elle fonctionne pour un type particulier qui est a priori inconnu. Si vous avez une fonction polymorphe, cela suffit, mais pour passer des fonctions polymorphes telles que des arguments, vous avez besoin de {-# LANGUAGE Rank2Types #-} :

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

LambdaCase

Une extension syntaxique qui vous permet d'écrire \case à la place de \arg -> case arg of .

Considérons la définition de fonction suivante:

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

Si vous voulez éviter de répéter le nom de la fonction, vous pourriez écrire quelque chose comme:

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

À l'aide de l'extension LambdaCase, vous pouvez écrire cela en tant qu'expression de fonction, sans avoir à nommer l'argument:

{-# LANGUAGE LambdaCase #-}

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

RankNTypes

Imaginez la situation suivante:

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

Ici, nous voulons passer une fonction qui convertit une valeur en une chaîne, appliquer cette fonction à la fois à un paramètre de chaîne et à un paramètre int et les imprimer tous les deux. Dans mon esprit, il n'y a aucune raison que cela échoue! Nous avons une fonction qui fonctionne sur les deux types de paramètres que nous transmettons.

Malheureusement, cela ne va pas vérifier GHC déduit a type basé sur sa première occurrence dans le corps de la fonction. C'est-à-dire dès que nous frappons:

putStrLn (show' string)

GHC déduira que show' :: String -> String , puisque string est une String . Il va exploser en essayant de show' int .

RankNTypes vous permet d'écrire la signature de type comme suit, en quantifiant toutes les fonctions qui satisfont le type de show' :

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

C'est le polymorphisme de rang 2: nous affirmons que la fonction show' doit fonctionner pour tous les a s de notre fonction, et que l'implémentation précédente fonctionne maintenant.

La RankNTypes extension permet l' imbrication arbitraire de forall ... blocs dans les signatures de type. En d'autres termes, il permet le polymorphisme de rang N.

Listes surchargées

ajouté dans GHC 7.8 .

OverloadedLists, similaire à OverloadedStrings , permet de désabuser les littéraux de liste comme suit:

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

Cela est pratique lorsque vous traitez des types tels que Set , Vector et Map s.

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

IsList classe IsList dans GHC.Exts est destinée à être utilisée avec cette extension.

IsList est équipé d'une fonction de type, Item , et trois fonctions, fromList :: [Item l] -> l , toList :: l -> [Item l] et fromListN :: Int -> [Item l] -> lfromListN est facultatif. Les implémentations typiques sont:

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

Exemples tirés de OverloadedLists - GHC .

Dépendances fonctionnelles

Si vous avez une classe de type multi-paramètres avec les arguments a, b, c et x, cette extension vous permet d'exprimer que le type x peut être identifié de manière unique à partir de a, b et c:

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

Lors de la déclaration d'une instance de cette classe, elle sera vérifiée par rapport à toutes les autres instances pour s'assurer que la dépendance fonctionnelle est maintenue, c'est-à-dire qu'aucune autre instance avec le même abc mais un x différent n'existe.

Vous pouvez spécifier plusieurs dépendances dans une liste séparée par des virgules:

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

Par exemple dans MTL on peut voir:

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

Maintenant, si vous avez une valeur de type MonadReader a ((->) Foo) => a , le compilateur peut en déduire a ~ Foo , puisque le second argument détermine complètement le premier et simplifiera le type en conséquence.

La classe SomeClass peut être considérée comme une fonction des arguments abc qui donnent x . De telles classes peuvent être utilisées pour effectuer des calculs dans le système de types.

GADT

Les types de données algébriques conventionnels sont paramétriques dans leurs variables de type. Par exemple, si on définit un ADT comme

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

avec l'espoir que cela exclut statiquement les conditions non bien typées, cela ne se comportera pas comme prévu puisque le type d' IntLit :: Int -> Expr a est quantifié IntLit :: Int -> Expr a : pour tout choix de a , il produit une valeur de type Expr a . En particulier, pour a ~ Bool , nous avons IntLit :: Int -> Expr Bool , ce qui nous permet de construire quelque chose comme If (IntLit 1) e1 e2 ce que le type du constructeur If essayait d'éliminer.

Les types de données algébriques généralisés nous permettent de contrôler le type résultant d'un constructeur de données afin qu'il ne soit pas simplement paramétrique. Nous pouvons réécrire notre Expr de type comme GADT comme celui - ci:

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

Ici, le type du constructeur IntLit est Int -> Expr Int , donc IntLit 1 :: Expr Bool ne va pas taper.

La correspondance de modèle sur une valeur GADT entraîne un affinement du type du terme renvoyé. Par exemple, il est possible d'écrire un évaluateur pour Expr a comme ceci:

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 

Notez que nous pouvons utiliser (+) dans les définitions ci-dessus parce que, par exemple, si IntLit x correspond à un motif, nous apprenons aussi a ~ Int (et de même pour not et if_then_else_ pour a ~ Bool ).

ScopedTypeVariables

ScopedTypeVariables vous permet de faire référence à des types universellement quantifiés dans une déclaration. Pour être plus explicite:

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)

L'important est que nous puissions utiliser a , b et c pour instruire le compilateur dans des sous-expressions de la déclaration (le tuple dans la clause where et le premier a dans le résultat final). En pratique, ScopedTypeVariables aide à écrire des fonctions complexes en tant que somme de parties, ce qui permet au programmeur d’ajouter des signatures de type à des valeurs intermédiaires qui n’ont pas de types concrets.

PatternSynonymous

Les synonymes de motif sont des abstractions de motifs similaires à la façon dont les fonctions sont des abstractions d'expressions.

Pour cet exemple, examinons l'interface que Data.Sequence expose et voyons comment il peut être amélioré avec des synonymes de modèle. Le type Seq est un type de données qui, en interne, utilise une représentation compliquée pour obtenir une bonne complexité asymptotique pour diverses opérations, notamment O (1) (un) consing et (un) snocing.

Mais cette représentation est lourde et certains de ses invariants ne peuvent pas être exprimés dans le système de types de Haskell. De ce fait, le type Seq est exposé aux utilisateurs en tant que type abstrait, avec des fonctions d’accesseur et de constructeur préservant les invariants, parmi lesquelles:

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

Mais utiliser cette interface peut être un peu lourd:

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

Nous pouvons utiliser des patterns de vue pour le nettoyer quelque peu:

{-# LANGUAGE ViewPatterns #-}

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

En utilisant l’extension de langage PatternSynonyms , nous pouvons donner une interface encore plus belle en autorisant l’appariement de motifs à prétendre que nous avons une liste de cons- ou de 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)

Cela nous permet d’écrire des uncons dans un style très naturel:

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

RecordWildCards

Voir RecordWildCards



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow