# Type-Level Features and GHC Extensions — Haskell

Source: https://www.geekswithgeeks.com/en/haskell/x-types

> 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.

![Two coins with different currency tags trying to enter an addition box; one pair with matching tags passes, the mismatched pair is blocked.](assets/figures/haskell/section-8-map.svg) — Figure 8.1 — Phantom types prevent mixing currencies.

## Phantom currency types and a GADT interpreter

DataKinds for currencies, GADTs for a well-typed expression language.

```haskell
{-# 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-check
```

## Complexity 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.

**Quiz:** What does a phantom type parameter such as c in Money c provide?

- [ ] Runtime currency conversion
- [ ] A mutable field
- [x] 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.
