如何在不增加索引的情况下增加`Fin n`的值?

时间:2018-04-09 14:30:38

标签: agda idris

当尝试在Idris上实现mod : Nat -> (m : Nat) -> Fin m函数时,我注意到明显的算法不起作用,因为当循环并且当增加结果时,Idris将不相信它仍在范围内。这段代码解释了这个问题:

-- (M : Nat) is the modulus, doesn't change
-- (r : Fin M) is the result, increases as you recurse
-- (n : Nat) is the dividend, the recursion stops when it is 0
-- (m : Nat) is the modulus index, when it is Zero, the result loops around
modAux : (M : Nat) -> (r : Fin M) -> (n : Nat) -> (m : Nat) -> Nat

-- When `n` is Zero, we're done, so, return result (works!)
modAux M r Z m = r

-- When `n > 0` but `m` iz zero, the result must loop around
-- Problem: `Z` is not a valid `Fin M`!
modAux M r (S n) Z = modAux M Z n M

-- when `n > 0` and `m > 0`, decrease both and increase the result
-- Problem: `(S r)` is not a valid `Fin M`!
modAux M r (S n) (S m) = modAux M (S r) n m

要实现modAux,我们似乎需要一个循环的suc : Fin n -> Fin n函数。我也很难实现这一点。在查看Agda的mod标准库实现时,我注意到它首先证明mod-lemma : (acc d n : ℕ) → let s = acc + n in mod-helper acc s d n ≤ s,然后使用mod来实现Fin.fromℕ≤″。那看起来很重。有没有其他方法可以在不增加索引的情况下增加Fin n值?

2 个答案:

答案 0 :(得分:4)

这是一个解决方案:

open import Function
open import Data.Nat
open import Data.Fin renaming (zero to fzero; suc to fsuc) using (Fin)

suc-around : ∀ {n} -> Fin n -> Fin n
suc-around {0}     ()
suc-around {suc _} i  = go id i where
  go : ∀ {n m} -> (Fin n -> Fin (suc m)) -> Fin n -> Fin (suc m)
  go {1}           k  fzero   = fzero
  go {suc (suc _)} k  fzero   = k (fsuc fzero)
  go               k (fsuc i) = go (k ∘ fsuc) i

我们的想法是,您需要决定在处理Fin.suc i的最后是否返回Fin.zeroFin n。即当您进行递归调用时,您不知道计算是否会导致额外的Fin.sucFin.zero将被返回。只有在您知道Fin类型的参数实际为Fin.zero的基本情况下才能进行此选择。这就是这两行的责任:

  go {1}           k  fzero   = fzero
  go {suc (suc _)} k  fzero   = k (fsuc fzero)

如果没有其他Fin.suc的空间(即参数实际上具有Fin 1类型),则只需返回Fin.zero。但是,如果可以在不更改类型的情况下应用另一个Fin.suc,那么在执行递归调用时执行此操作并应用包含到目前为止收集的Fin.suc的延续:

  go               k (fsuc i) = go (k ∘ fsuc) i

所以主要的想法是你需要选择在基本情况下返回的内容,在递归的情况下你只能确保你不会丢失已处理了多少Fin.suc的信息

关于mod本身,我喜欢thisAndrás Kovács实施。

答案 1 :(得分:4)

Data.Fin.strengthen可以完成繁重的工作。

-- Data.Fin.strengthen : Fin (S n) -> Either (Fin (S n)) (Fin n)
-- conceptually
-- strengthen Data.Fin.last = Left last
-- strengthen n = Right n

就个人而言,我不喜欢这个功能,因为它的输出太大了#34 ;;它通常不会使用它可以返回的所有Left值,但它会在标准库中运行。

rotateUp : Fin n -> Fin n
rotateUp {n = Z} _ impossible
rotateUp {n = S k} f = either (const FZ) FS $ strengthen f

我不建议直接撰写modAux。我宁愿用这个函数来表达mod,这更通用:

toChurchNat : Nat -> (a -> a) -> (a -> a)
toChurchNat Z _ x = x
toChurchNat (S n) f x = toChurchNat n f (f x)

Nat转换为其教会表示(一个多次重复任何内功能的函数)。因此:

mod : Nat -> (divisor : Nat) -> {auto prf : IsSucc divisor} -> Fin divisor
mod dividend (S divisor') = toChurchNat dividend rotateUp FZ