I was trying to understand what the FlexibleContexts extension is doing by searching for web pages that would explain it to mere mortals (people who have read LYHFGG, for example, like me) but I did not find any such resource.
Therefore I ask the experts on the topic: Could you please explain what this extension does, why it exists, and give one or two simple examples how and why one should use it?
Furthermore, If I am reading someone else's code which uses this extension, then what should I know about the extension in order to understand the code written using this extension?
Without FlexibleContexts
all typeclass constraints on function definitions must have type variables. For example:
add :: Num a => a -> a -> a
add = (+)
Where a
is the type variable. With FlexibleContexts
enabled you can have any type inside a typeclass.
intAdd :: Num Int => Int -> Int -> Int
intAdd = (+)
This example is pretty contrived but it is the simplest I can think of. FlexibleContexts
is usually only used with MultiParamTypeClasses
. Here is an example:
class Shower a b where
myShow :: a -> b
doSomething :: Shower a String => a -> String
doSomething = myShow
Here you can see we say that we only want a Shower a String
. Without FlexibleContexts
String
would have to be a type variable instead of a concrete type.
Commonly it's used with the MultiParamTypeClasses
extension, for example when using the mtl
library you might write
doSomethingWithState :: MonadState MyState m => m ()
doSomethingWithState = do
current <- get
let something1 = computeSomething1 current
something2 = computeSomething2 current something1
put something2
And similarly with MonadReader
and MonadWriter
, along with other similar typeclasses. Without FlexibleContexts
you can't use this constraint.
(Note that this answer was based on @DiegoNolan's but rewritten to use an existing library that should make sense to LYAH readers).
I've discovered a use for it apart from those mentioned: it results in clearer error messages from GHC. E.g. normally,
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)
And with FlexibleContexts enabled:
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
Here's a discussion.
FlexibleContexts
is often used with type families. For example, when using GHC.Generics
, it's common to see signatures like
foo :: (Generic a, GFoo (Rep a)) => Int -> a -> a
This can be seen as a variation of the MultiParamTypeClasses
usage:
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
As AJFarmar pointed out, FlexibleContexts
is also useful with neither MPTCs nor type families. Here's a simple example:
newtype Ap f a = Ap (f a)
deriving instance Show (f a) => Show (Ap f a)
The alternative approach using Show1
is significantly more awkward.
A more involved example is provided by AJFarmar's comment:
data Free f a = Pure a | Free (f (Free f a))
deriving instance (Show a, Show (f (Free f a))) => Show (Free f a)
This brings in UndecidableInstances
as well, since it's recursive, but it does a good job of explaining just what it needs to be able to show Free f a
. In bleeding-edge GHC Haskell, an alternative would be to use QuantifiedConstraints
:
deriving instance (Show a, forall x. Show x => Show (f x)) => Show (Free f a)
but this is overkill because we only need to show f
applied to Free f a
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With