Is the extension GADT
in Haskell destroying polymorphism, even in code that don't use GADTs ?
Here's a example which works and don't use GADT
{-# LANGUAGE RankNTypes #-}
--{-# LANGUAGE GADTs #-}
newtype Fix1 f i = In1 { unFix1 :: f (Fix1 f) i }
mcata1 :: (forall r k. (forall j. r j -> t j) -> f r k -> t k)
-> Fix1 f i
-> t i
mcata1 f (In1 x) = f (mcata1 f) x
newtype Ret i = Ret {unRet :: (i -> Int) -> Int}
-- recusive type
data BushR i = NBR | CBR i (BushR (BushR i))
-- initial algebra
data BushF r i = NB | CB i (r (r i))
type Bush i = Fix1 BushF i
bsumm = mcata1 alg
where alg :: forall i r. (forall j. r j -> Ret j) -> BushF r i -> Ret i
alg r NB = Ret (const 0)
alg r (CB x xs) = Ret (\f ->
f x + bsum xs (\ys -> bsum ys f))
where
bsum = unRet . r
If GADTs
is enabled we get a compile error
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
newtype Fix1 f i = In1 { unFix1 :: f (Fix1 f) i }
mcata1 :: (forall r k. (forall j. r j -> t j) -> f r k -> t k)
-> Fix1 f i
-> t i
mcata1 f (In1 x) = f (mcata1 f) x
newtype Ret i = Ret {unRet :: (i -> Int) -> Int}
-- recusive type
data BushR i = NBR | CBR i (BushR (BushR i))
-- initial algebra
data BushF r i = NB | CB i (r (r i))
type Bush i = Fix1 BushF i
bsumm = mcata1 alg
where alg :: forall i r. (forall j. r j -> Ret j) -> BushF r i -> Ret i
alg r NB = Ret (const 0)
alg r (CB x xs) = Ret (\f ->
f x + bsum xs (\ys -> bsum ys f)) -- error
where
bsum = unRet . r
-- • Occurs check: cannot construct the infinite type: i1 ~ r i1
-- Expected type: r (r i1)
-- Actual type: r i1
-- • In the first argument of ‘bsum’, namely ‘ys’
-- In the expression: bsum ys f
-- In the second argument of ‘bsum’, namely ‘(\ ys -> bsum ys f)’
-- • Relevant bindings include
which can be fixed by duplicating bindings
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
newtype Fix1 f i = In1 { unFix1 :: f (Fix1 f) i }
mcata1 :: (forall r k. (forall j. r j -> t j) -> f r k -> t k)
-> Fix1 f i
-> t i
mcata1 f (In1 x) = f (mcata1 f) x
newtype Ret i = Ret {unRet :: (i -> Int) -> Int}
-- recusive type
data BushR i = NBR | CBR i (BushR (BushR i))
-- initial algebra
data BushF r i = NB | CB i (r (r i))
type Bush i = Fix1 BushF i
bsumm = mcata1 alg
where alg :: forall i r. (forall j. r j -> Ret j) -> BushF r i -> Ret i
alg r NB = Ret (const 0)
alg r (CB x xs) = Ret (\f ->
f x + bsum xs (\ys -> bsum2 ys f))
where
bsum = unRet . r
bsum2 = unRet . r
Is there a deep reason for that ?
The GADTs extension also sets GADTSyntax and MonoLocalBinds. A GADT can only be declared using GADT-style syntax ( Declaring data types with explicit constructor signatures ); the old Haskell 98 syntax for data declarations always declares an ordinary data type.
The key point about GADTs is that pattern matching causes type refinement. For example, in the right hand side of the equation eval :: Term a -> a eval (Lit i) = ... the type a is refined to Int. That’s the whole point!
It is permitted to declare an ordinary algebraic data type using GADT-style syntax. What makes a GADT into a GADT is not the syntax, but rather the presence of data constructors whose result type is not just T a b.
In a GADT, the product constructors (called data constructors in Haskell) can provide an explicit instantiation of the ADT as the type instantiation of their return value. This allows defining functions with a more advanced type behaviour.
Enabling GADTs
we implicitly also enable MonoLocalBinds
which prevents some forms of let
- and where
- type generalizations.
This affects types whose type variables are partly quantified (i
below), and partly free (r
below).
where bsum :: forall i. r i -> (i -> Int) -> Int
bsum = unRet . r
A simple solution is to provide an explicit type annotation so to force the wanted generalization.
The docs provide a rationale for MonoLocalBinds
, showing why it makes sense to restrict generalization in some cases. A blog post from 2010 provides further discussion.
A relevant quote by Simon Peyton-Jones from the blog (slightly reformatted, and using the same links as in the original blog):
Why did we make it?
It’s a long story, but the short summary is this: I don’t know how to build a reliable, predictable type inference engine for a type system that has both
- (a) generalisation of local let/where and
- (b) local type equality assumptions, such as those introduced by GADTs.
The story is told in our paper “Let should not be generalised” and, at greater length, in the journal version “Modular type inference with local assumptions”.
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