Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can finite numbers work? (dependent types)

I'm interested in dependently typed languages. Finite numbers seem very usable to me. For example, to safely index fixed-size arrays. But the definition is not clear for me.

The data type for finite numbers in Idris can be the following: (And probably similar in Agda)

data FiniteNum : Natural -> Type where
  FZero : FiniteNum (Succ k)
  FSucc : FiniteNum k -> FiniteNum (Succ k)

And it seems to work:

exampleFN : FiniteNum (Succ (Succ Zero))
exampleFN = FSucc FZero -- typechecks
-- exampleFN = FSucc (FSucc FZero)  -- won't typecheck

But how does this work? What does k mean? And how come that the type checker accepts the first implementation and rejects the second?

like image 820
Boldizsár Németh Avatar asked Sep 10 '13 18:09

Boldizsár Németh


2 Answers

Think about the index as an upper bound for any number that can be represented by Fin n. For example, Fin 4 contains all natural numbers less than 4. Here's a data declaration:

data Fin : ℕ → Set where

How does the definition relate to this? The definition of natural numbers has two parts: zero and suc; for Fin, we'll call those fzero and fsuc.

With our upper bound interpretation, fzero can be given any upper bound as long as it is not zero (0 ≮ 0). How do we represent that the bound can be anything other than zero? We can force that simply by applying suc:

fzero : {k : ℕ} → Fin (suc k)

This gives us precisely what we need: no-one can write fzero in such a way that its type is Fin 0.

Now the fsuc case: we have a number with some upper bound k and we want to create a successor. What can we tell about the upper bound? It surely increases by at least one:

fsuc : {k : ℕ} → Fin k → Fin (suc k)

As an exercise, convince yourself that all numbers less than n can be represented by Fin n (in only one possible way).


How does the type checker accept one and reject another? In this case it's simple unification. Let's take a look at this code:

test : Fin (suc (suc zero))
test = ?

When we write fsuc, Agda has to figure out the value of k. To do that, it takes a look at fsuc constructor: it constructs a value of type Fin (suc k) and we need suc k = suc (suc zero); by unifying these two, we get that k = suc zero. So next:

test = fsuc ?

Now, the expression following fsuc (represented by ?, a hole) has a type Fin (suc zero) (since k = suc zero). When we fill in fzero, Agda tries to unify suc zero with suc k₂, which of course succeeds with the solution k₂ = zero.

If we decide to throw in another fsuc:

test = fsuc (fsuc ?)

Then using the same unification as above, we get that the type of the hole must be Fin zero. So far this type checks just fine. However, when you try to stuff fzero in there, Agda has to unify zero with suc k₃ - no matter the value of k₃, suc of something can never be zero, so that fails and you get a type error.


Another representation of finite numbers (that is arguably less plesant to work with) is a pair of natural number and a proof that it is less than the bound.

open import Data.Product

Fin' : ℕ → Set
Fin' n = Σ ℕ (λ k → k < n)

The original Fin can be thought as a version of Fin' where the proof is baked directly into the constructors.

like image 114
Vitus Avatar answered Oct 28 '22 18:10

Vitus


The short answer is that the FiniteNum Zero type is empty, because both constructors return FiniteNum indexed with a non-zero natural number. Now try to answer the following: how many elements does FiniteNum (Succ Zero) have? How do they look like? Repeat for 2,3,4...

like image 33
wjedynak Avatar answered Oct 28 '22 16:10

wjedynak