पाठ 22 / 25
Type-Level Features and GHC Extensions
Explore GADTs, phantom types, type families and language editions.
Going further with types
GHC extends Haskell 2010 with many language extensions, enabled per file with {-# LANGUAGE ... #-} or per project. Language editions bundle stable ones: GHC2021 (and the newer GHC2024) enable widely used extensions such as ScopedTypeVariables, TypeApplications, DeriveFunctor and LambdaCase. Powerful type-level features include phantom types, type parameters that do not appear in values but distinguish them (Money 'INR vs Money 'USD with DataKinds); GADTs (generalised algebraic data types), whose constructors can refine the result type, enabling type-safe interpreters; type families, functions on types; RankNTypes for functions that take polymorphic arguments; DerivingVia for reusing instances; and type-level literals (Symbol, Nat). Libraries such as Servant and many database libraries rely on them. These tools encode more invariants in types, but they increase complexity and error-message difficulty. Use them where the guarantee is worth it, and keep most application code in plain, simple Haskell (sometimes called "simple Haskell" or "boring Haskell").
Types that carry more information
Phantom and indexed types let the compiler reject invalid combinations.
Phantom currency types and a GADT interpreter
DataKinds for currencies, GADTs for a well-typed expression language.
{-# LANGUAGE DataKinds, KindSignatures, GADTs #-}
-- Phantom types: the currency exists only at the type level
data Currency = INR | USD
newtype Money (c :: Currency) = Money Int
deriving Show
addMoney :: Money c -> Money c -> Money c
addMoney (Money a) (Money b) = Money (a + b)
price :: Money 'INR
price = Money 49900
shipping :: Money 'INR
shipping = Money 4900
-- addMoney price (Money 10 :: Money 'USD) -- compile error: INR vs USD
-- GADT: each constructor states the type of value it produces
data Expr a where
IntE :: Int -> Expr Int
BoolE :: Bool -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
Equal :: Expr Int -> Expr Int -> Expr Bool
If :: Expr Bool -> Expr a -> Expr a -> Expr a
eval :: Expr a -> a
eval (IntE n) = n
eval (BoolE b) = b
eval (Add x y) = eval x + eval y
eval (Equal x y) = eval x == eval y
eval (If c t e) = if eval c then eval t else eval e
main :: IO ()
main = do
print (addMoney price shipping) -- Money 54800
print (eval (If (Equal (IntE 2) (Add (IntE 1) (IntE 1))) (IntE 10) (IntE 20))) -- 10
-- Add (BoolE True) (IntE 1) would not type-checkComplexity budget
Each advanced type feature adds a cost for every reader of the code. Teams often agree on a set of allowed extensions and reserve type-level tricks for libraries and the most critical invariants.
त्वरित जाँच: What does a phantom type parameter such as c in Money c provide?
- Runtime currency conversion
- A mutable field
- A compile-time tag that prevents mixing values with different tags, with no runtime cost
- A JSON encoding
Answer
A compile-time tag that prevents mixing values with different tags, with no runtime cost — The parameter exists only in types, so mismatches are caught by the compiler.