J'essayais de comprendre ce que faisait l'extension FlexibleContexts en recherchant des pages Web qui l'expliqueraient à de simples mortels (des gens qui ont lu LYHFGG, par exemple, comme moi), mais je n'ai trouvé aucune ressource de ce type.
Par conséquent Je demande aux experts sur le sujet: pourriez-vous expliquer ce que fait cette extension, pourquoi elle existe, et donner un ou deux exemples simples comment et pourquoi on devrait l'utiliser?
De plus , si je lis le code de quelqu'un d'autre qui utilise cette extension, alors que dois-je savoir sur l'extension afin de comprendre le code écrit en utilisant cette extension?
Sans FlexibleContexts
toutes les contraintes de classe de types sur les définitions de fonction doivent avoir des variables de type. Par exemple:
add :: Num a => a -> a -> a
add = (+)
Où a
est la variable de type. Avec FlexibleContexts
activé, vous pouvez avoir n'importe quel type dans une classe de types.
intAdd :: Num Int => Int -> Int -> Int
intAdd = (+)
Cet exemple est assez artificiel mais c'est le plus simple auquel je puisse penser. FlexibleContexts
n'est généralement utilisé qu'avec MultiParamTypeClasses
. Voici un exemple:
class Shower a b where
myShow :: a -> b
doSomething :: Shower a String => a -> String
doSomething = myShow
Ici, vous pouvez voir que nous disons que nous voulons seulement un Shower a String
. Sans FlexibleContexts
String
devrait être une variable de type au lieu d'un type concret.
Généralement, il est utilisé avec l'extension MultiParamTypeClasses
, par exemple lorsque vous utilisez la bibliothèque mtl
que vous pourriez écrire
doSomethingWithState :: MonadState MyState m => m ()
doSomethingWithState = do
current <- get
let something1 = computeSomething1 current
something2 = computeSomething2 current something1
put something2
Et de la même façon avec MonadReader
et MonadWriter
, ainsi que d'autres classes de types similaires. Sans FlexibleContexts
, vous ne pouvez pas utiliser cette contrainte.
(Notez que cette réponse était basée sur @ DiegoNolan's mais réécrite pour utiliser une bibliothèque existante qui devrait avoir du sens pour les lecteurs de LYAH).
J'ai découvert une utilisation en dehors de celles mentionnées: il en résulte des messages d'erreur plus clairs de GHC. Par exemple. normalement,
Prelude> max (1, 2) 3
<interactive>:1:1: error:
• Non type-variable argument in the constraint: Num (a, b)
(Use FlexibleContexts to permit this)
• When checking the inferred type
it :: forall a b.
(Num (a, b), Num b, Num a, Ord b, Ord a) =>
(a, b)
Et avec FlexibleContexts activé:
Prelude> max (1, 2) 3
<interactive>:1:1: error:
• No instance for (Num (Integer, Integer))
arising from a use of ‘it’
• In the first argument of ‘print’, namely ‘it’
In a stmt of an interactive GHCi command: print it
Voici ne discussion .
FlexibleContexts
est souvent utilisé avec les familles de types. Par exemple, lorsque vous utilisez GHC.Generics
, il est courant de voir des signatures comme
foo :: (Generic a, GFoo (Rep a)) => Int -> a -> a
Cela peut être vu comme une variation de l'utilisation de MultiParamTypeClasses
:
class (Generic a, rep ~ Rep a) => MPGeneric rep a
instance (Generic a, rep ~ Rep a) => MPGeneric rep a
mpFoo :: (MPGeneric rep a, GFoo rep) => Int -> a -> a
Comme AJFarmar l'a souligné , FlexibleContexts
est également utile sans MPTC ni familles de types. Voici un exemple simple:
newtype Ap f a = Ap (f a)
deriving instance Show (f a) => Show (Ap f a)
L'approche alternative utilisant Show1
est beaucoup plus gênant.
Un exemple plus impliqué est fourni par le commentaire d'AJFarmar:
data Free f a = Pure a | Free (f (Free f a))
deriving instance (Show a, Show (f (Free f a))) => Show (Free f a)
Cela apporte également UndecidableInstances
, car il est récursif, mais il explique bien ce dont il a besoin pour pouvoir montrer Free f a
. Dans GHC Hedgeell à fond perdu, une alternative serait d'utiliser QuantifiedConstraints
:
deriving instance (Show a, forall x. Show x => Show (f x)) => Show (Free f a)
mais c'est exagéré car nous n'avons qu'à montrer f
appliqué à Free f a
.