问题
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?
回答1:
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.
回答2:
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...
来源:https://stackoverflow.com/questions/18726164/how-can-finite-numbers-work-dependent-types