I’ve recently been spending a decent amount of my free time in self-study of Ulrich Kohlenbach’s Applied Proof Theory: Proof Interpretations and their Use in Mathematics. As someone who has no experience with Constructivism, it is definitely challenging, but there is no better way to learn than hands-on. Here I explore one of the topics the book mentions – encoding rational numbers into the naturals – using Haskell.
The idea is simple – take operations on
The gist is available here.
Before we jump in, we’ll establish some prerequisite machinery for our exploration. First, we’ll take the Nat instance from the exploration on infinite search and expand it a bit using the nomenclature in Kohlenbach. Next, we’ll borrow the least function from Seemingly Impossible Functional Programs, slightly modified:
least p = if p 0 then 0 else 1 + least(\n -> p (1 + n))
Lastly, we’ll build the one-point compactification of
natural :: Set Nat
natural = union (singleton Z) (S <$> natural)
Now we’re ready for math.
Rationals can be thought of as pairs of integers, one being the numerator
- The encoding has two inputs and one output.
- Rationals have sign, but the naturals have no sign.
We’ll tackle 2 first. If
Now that we have
The function numden2QCode below performs this encoding, returning a natural number of type QCode representing a rational from the rational’s numerator and denominator.
numden2QCode :: Integer -> Integer -> QCode
numden2QCode num den | (num >= 0 && den > 0) || (num <= 0 && den < 0) = j (fromInteger $ 2*abs(num)) (fromInteger $ abs(den)-1)
| otherwise = j (fromInteger $ 2*abs(num)-1) (fromInteger $ abs(den)-1)
j :: Nat -> Nat -> QCode
j x y = if k <= t then QCode k else QCode 0
where k = least (\u -> 2*u == t)
t = (x+y)*(x+y) + 3*x + y
Testing for equivalency of rationals using QCodes might seem like a straightforward process of comparing the QCodes themselves, but one characteristic of the numden2QCode encoding we’ve developed is that
Here,
>j1 $ j 2 3
S (S Z)
>j2 $ j 3 4
S (S (S (S Z)))
Lastly, to test equivalence, we’ll extend QCode to be an instance of the Eq typeclass. The trick here is that we need to handle odd and even
instance Eq QCode where
(==) n1 n2 | evenNat j1n1 && evenNat j1n2 = (j1n1/2)*(j2n2 + 1) == (j1n2/2)*(j2n1 + 1)
| oddNat j1n1 && oddNat j1n2 = ((j1n1+1)/2)*(j2n2 + 1) == ((j1n2+1)/2)*(j2n1 + 1)
| otherwise = False
where j1n1 = j1 n1
j1n2 = j1 n2
j2n1 = j2 n1
j2n2 = j2 n2
Finally, we can equate some rationals!
print $ numden2QCode (-1) 1 == numden2QCode (-2) 2 -- True
print $ numden2QCode (-1) 1 == numden2QCode (-3) 3 -- True
print $ numden2QCode (-1) 2 == numden2QCode 2 (-4) -- True
print $ numden2QCode 1 2 == numden2QCode 2 4 -- True
print $ numden2QCode 1 2 == numden2QCode 1 3 -- False
Note that you’ll probably want to compile these into an executable like I did because they are slow otherwise. In executable form, they are done in about 1.5 seconds. In a future post, I’ll explore why a few changes to the Num instance for Nat reduced execution time from 88 seconds down to 1.5.