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?
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.
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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With