EDIT: A comment above has provided the missing piece. Some people are deliberately playing with less-than-turing-complete languages. I explicitly don't care about such languages. A really usuable not-turing-complete language is a crazy hard thing to design. The whole rest of this expands on what happens trying to apply these theorems to a full language.
False!
function f(a): forall t: Type, t->t
function g(a): forall t: Type, t->t
return (a is g) ? f : a
return a is f ? g : a
where the is
operator compares two variables for reference identity. That is, they contain the same value. Not an equivalent value, same value. Functions f
and g
are equivalent by some definition but they aren't the same.
If this function is passed itself it returns something else; otherwise it returns its input. The something else has the same type as itself therefore it can be substituted. In other words, f
is not the identity, because f(f)
returns g
, whereas the identity would return f
.
For the theorem to hold it has to assume the ridiculous ability to reduce
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(1000, <0, a>)[1]¹
If you're willing to assume that you can assume the much easier type inference can be handled.
If we try to restrict the domain until the theorem holds we end up having to restrict it awfully far.
- Pure Functional (no mutable state, no IO). OK I can live with that. A lot of time we want to run proofs over functions.
- Empty standard library. meh.
- No
raise
and no exit
. Now we're starting to get constrained.
- There is no bottom type.
- The language has a rule that allows the compiler to collapse infinite recursion by assuming it must terminate. The compiler is allowed to reject trivial infinite recursion.
- The compiler is allowed to fail if presented with something that can't be proved either way.² Now the standard library can't take functions as arguments. Boo.
- There is no
nil
. This is starting to get problematic. We've ran out of ways to deal with 1/0.³
- The language can't do branch type inferences and does not have an override for when the programmer can prove a type inference the language cant. This is pretty bad.
The existence of both of the last two constraints has crippled the language. While it is still Turing complete the only way to get general purpose work out of it is to simulate an inner platform that interprets a language with looser requirements.
¹ If you think the compiler can deduce that one, try this one
function fermat(z) : int -> int
function pow(x, p)
return p = 0 ? 1 : x * pow(x, p - 1)
function f2(x, y, z) : int, int, int -> <int, int>
left = pow(x, 5) + pow(y, 5)
right = pow(z, 5)
return left = right
? <x, y>
: pow(x, 5) < right
? f2(x + 1, y, z)
: pow(y, 5) < right
? f2(2, y + 1, z)
: f2(2, 2, z + 1)
return f2(2, 2, z)
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(fermat(3)[0], <0, a>)[1]
² The proof that the compiler can't do this depends on blinding. We can use multiple libraries to ensure the compiler can't see the loop at once. Also, we can always construct something where the program would work but could not be compiled because the compiler can't perform the induction in available memory.
³ Somebody thinks you can have this return nil without arbitrary generic types returning nil. This pays a nasty penalty for which I have seen no effective language that can pay it.
function f(a, b, c): t: Type: t[],int,int->t
return a[b/c]
must not compile. The fundamental problem is runtime array indexing doesn't work anymore.