Decidability in the One-Point Compactification of ℕ

Table of Contents

1 Introduction

:TODO: More narrative here.

From a recent Agda mailing list post by Martin Escardo.

We can write the following unlikely functional program in Agda (and in Martin-Loef Type Theory of any variety):

(1) Consider the type of decreasing infinite sequences of digits 0 and 1.

Call this type ℕ∞, because

  • The sequence of n-many ones followed by infinitely many zeros encodes the natural number n.
  • The sequence of infinitely many ones encodes ∞,

So we have an embedding ℕ → ℕ∞ and a new point ∞ in ℕ∞.

(2) There is an Agda program that decides, for a given p : ℕ∞ → Bool, whether or not there is x : ℕ∞ with p x ≡ True.

Clearly such a program does not exist if we replace ℕ∞ by ℕ. This non-existence is a theorem of computability theory.

But it does exist with ℕ∞.

2 Some small extensions to the standard library

These I found useful at least at some stage of development though I did not end up using all of them. They may already be implemented in the standard library, just not where I looked; I'd have to double check.

nothing≢just : {A : Set}(a : A) → ¬ (nothing ≡ just a)
nothing≢just a ()

Is-nothing? : {A : Set}(x : Maybe A) → Dec (Is-nothing x)
Is-nothing? nothing = yes All.nothing
Is-nothing? (just _) = no λ { (All.just ()) }

Is-just? : {A : Set}(x : Maybe A) → Dec (Is-just x)
Is-just? nothing = no λ ()
Is-just? (just _) = yes (Any.just tt)

3 The one-point compactification of ℕ

First, let us use the name ℕ*, rather than ℕ∞. This leads us to naturally append * to the standard names for elements of or functions on ℕ*, which is far more readable than appending everywhere. We will still call the “new point” of ℕ* .

Rather than decreasing infinite sequences of 0's and 1's, I choose to represent ℕ* as a potentially infinite string of unary constructors; specifically, the constructor just of the Maybe type.

record ℕ* : Set where
  coinductive
  field
    c : Maybe ℕ*
open ℕ*

We need to define bisimilarity of elements of ℕ* in order to be able to compare them.

record _≈_ (m n : ℕ*) : Set where
  coinductive
  field
    c-≈ : ℕ*.c m ≡ ℕ*.c n
open _≈_

Symmetry and transitivity follow easily.

≈-sym : {m n : ℕ*} → m ≈ n → n ≈ m
c-≈ (≈-sym m≈n) = sym (c-≈ m≈n)

≈-trans : {m n o : ℕ*} → m ≈ n → n ≈ o → m ≈ o
c-≈ (≈-trans m≈n n≈o) = trans (c-≈ m≈n) (c-≈ n≈o)

4 Naming some elements of ℕ*

It's convenient to refer to the elements of ℕ* by these more familiar names.

0* : ℕ*
c 0* = nothing

suc* : ℕ* → ℕ*
c (suc* n) = just n

∞ : ℕ*
c ∞ = just ∞

Now we provide a series of proofs that these names refer to the numbers we have in mind; namely, we want to prove that

  • zero is not a successor1
  • successors is an injecive function,
  • infinity is a fix-point of successor, and
  • infinity is the only fix-point of successor.
¬[0*≈suc*-n] : (n : ℕ*) → ¬ 0* ≈ suc* n
¬[0*≈suc*-n] n 0*≈suc*-n = nothing≢just n (c-≈ 0*≈suc*-n)

¬[0*≈∞] : ¬ (0* ≈ ∞)
¬[0*≈∞] 0*≈∞ = nothing≢just ∞ (c-≈ 0*≈∞)
suc*-∞≈∞ : suc* ∞ ≈ ∞
c-≈ (suc*-∞≈∞) = refl
suc*-injective : {m n : ℕ*}
      → suc* m ≈ suc* n → m ≈ n
c-≈ (suc*-injective m≈n) = cong c (just-injective (c-≈ m≈n))
¬[n≈∞]⇒¬[suc*-n≈∞] : (n : ℕ*) → ¬ (n ≈ ∞) → ¬ (suc* n ≈ ∞)
¬[n≈∞]⇒¬[suc*-n≈∞] n ¬[n≈∞] suc*-n≈∞ =
  ¬[n≈∞] (suc*-injective (≈-trans suc*-n≈∞
                                  (≈-sym suc*-∞≈∞)))

5 The embedding of ℕ into ℕ*

Now the embedding of a natural number n into produces a string of just's of length n, followed by a nothing. We could have used our constants here, but we define it directly instead.

embed : ℕ → ℕ*
embed zero = 0*
embed (suc n) = suc* (embed n)

There is no way that we can obtain the fix-point infinity by embedding.

¬[embed-n≈∞] : (n : ℕ) → ¬ (embed n ≈ ∞)
¬[embed-n≈∞] zero = ¬[0*≈∞]
¬[embed-n≈∞] (suc n) = ¬[n≈∞]⇒¬[suc*-n≈∞] (embed n) (¬[embed-n≈∞] n)

6 Decidable case checks on ℕ*

It may be useful for us to be able to distinguish values of ℕ*, because we cannot pattern match on elements. Decidable Is-zero* and Is-suc* predicates allow us to work around that limitation.

Is-zero* : ℕ* → Set
Is-zero* = _≈ 0*

Is-suc* : ℕ* → Set
Is-suc* n = Σ[ m ∈ ℕ* ] n ≈ suc* m

We can define an Is-∞ predicate, but unless I miss my mark, this cannot be decidable.

Is-∞ : ℕ* → Set
Is-∞ = _≈ ∞

I cannot see a way around using inspect to define the deciders for these. In the no cases, we must somewhat carefully constuct the contradictory value.

Is-zero*? : (n : ℕ*) → Dec (Is-zero* n)
Is-zero*? n with c n | inspect c n
... | nothing | [ c-n≡nothing ] = yes (record { c-≈ = c-n≡nothing })
... | just predn | [ c-n≡just-predn ]  =
  no λ n≈0* → ¬[0*≈suc*-n] predn (≈-sym (≈-trans (record { c-≈ = sym c-n≡just-predn }) n≈0*))

:TODO: commentary; I needed these for the approach to Is-suc*? which uses Is-zero*?, in order to translate the proofs.

pred* : (n : ℕ*) → ¬ (Is-zero* n) → ℕ*
pred* n ¬Is-zero*-n with c n | inspect c n
… | nothing | [ c-n≡nothing ] = ⊥-elim (¬Is-zero*-n (record { c-≈ = c-n≡nothing }))
… | just predn | [ _ ] = predn

suc*-pred* : (n : ℕ*)(¬Is-zero*-n : ¬ (Is-zero* n))
           → suc* (pred* n ¬Is-zero*-n) ≈ n
suc*-pred* n ¬Is-zero*-n with c n | inspect c n
… | nothing | [ c-n≡nothing ] = ⊥-elim (¬Is-zero*-n (record { c-≈ = c-n≡nothing }))
… | just predn | [ c-n≡just-predn ] = ≈-sym (record { c-≈ = c-n≡just-predn })

¬[Is-zero*-n]⇒Is-suc*-n : (n : ℕ*) → ¬ (Is-zero* n) → Is-suc* n
¬[Is-zero*-n]⇒Is-suc*-n n ¬Is-zero*-n = pred* n ¬Is-zero*-n
                                       , ≈-sym (suc*-pred* n ¬Is-zero*-n)

It seems natural to decide if a ℕ* is a successor by first deciding if it is zero.

Is-suc*? : (m : ℕ*) → Dec (Is-suc* m)
Is-suc*? m = case Is-zero*? m of
  λ { (yes Is-zero*-m)
        → no λ { (n , m≈suc*-n) → ¬[0*≈suc*-n] n (≈-trans (≈-sym Is-zero*-m)
                                                          m≈suc*-n) }
    ; (no ¬Is-zero*-m) → yes (¬[Is-zero*-n]⇒Is-suc*-n m ¬Is-zero*-m)
    }

7 Decidability of arbitrary predicates on ℕ*

:TODO: 😀

I had begun a naive approach before I began using bisimilarity above, but it quickly got rejected by the termination checker.

Specifically, it begins by checking p 0*, then if that fails it moves on with a modified predicate I called Suc p. That works fine, until we examine the result of running decide on that predicate.

All the same, I will likely return to it next, unless I suddenly see the correct way forward. The current source code is commented out in the Org source of this document. It needs refactoring, so I don't want to show it yet.

Footnotes:

1

From this, we could prove the corollary that zero is not infinity, but this would require a use of transitivity, so we provide a direct proof.

Author: Mark Armstrong markarmstrong.jpg

Contact: markparmstrong@gmail.com

Original date: August 25, 2020

Last updated: 2021-01-27 Wed 02:14

Created using Emacs 27.0.90 (Org mode 9.4.4)

Validate