An example how such a data type allows us to inhabit any type is given in Turner, D.A. (2004-07-28), Total Functional Programming, sect. 3.1, page 758 in Rule 2: Type recursion must be covariant."
Let's make a more elaborate example using Haskell. We'll start with a "bad" recursive data type
data Bad a = C (Bad a -> a)
and construct the Y combinator from it without any other form of recursion. This means that having such a data type allows us to construct any kind of recursion, or inhabit any type by an infinite recursion.
The Y combinator in the untyped lambda calculus is defined as
Y = λf.(λx.f (x x)) (λx.f (x x))
The key to it is that we apply x
to itself in x x
. In typed languages this is not directly possible, because there is no valid type x
could possibly have. But our Bad
data type allows this modulo adding/removing the constructor:
selfApp :: Bad a -> a
selfApp (x@(C x')) = x' x
Taking x :: Bad a
, we can unwrap its constructor and apply the function inside to x
itself. Once we know how to do this, it's easy to construct the Y combinator:
yc :: (a -> a) -> a
yc f = let fxx = C (\x -> f (selfApp x)) -- this is the λx.f (x x) part of Y
in selfApp fxx
Note that neither selfApp
nor yc
are recursive, there is no recursive call of a function to itself. Recursion appears only in our recursive data type.
We can check that the constructed combinator indeed does what it should. We can make an infinite loop:
loop :: a
loop = yc id
or compute let's say GCD:
gcd' :: Int -> Int -> Int
gcd' = yc gcd0
where
gcd0 :: (Int -> Int -> Int) -> (Int -> Int -> Int)
gcd0 rec a b | c == 0 = b
| otherwise = rec b c
where c = a `mod` b