假设我想将Fibonacci函数定义为以下函数:
fibo : Int -> Int
fibo 1 = 1
fibo 2 = 2
fibo n = fibo (n-1) + fibo (n-2)
这个函数显然不是完全的,因为它对于低于1的整数是未定义的,所以我需要以某种方式约束输入参数..
我尝试过定义新的数据类型MyInt
。一些事情:
-- bottom is the lower limit
data MyInt : (bottom: Int) -> (n: Int) -> Type
where
...
fibo : MyInt 1 n -> Int
...
但是我很快就迷路了。
如何将输入参数约束为例如my fibo
函数为1或更大的整数值?
答案 0 :(得分:4)
实际上有两个原因可以解释为什么Idris不会将fibo
函数识别为总数。首先,正如您所指出的,它没有为小于1的整数定义,但其次,它以递归方式调用自身。尽管Idris能够识别递归函数的总体,但它通常只能在可以显示递归调用的参数比原始参数更小(即更接近基本情况*)时才这样做(例如,如果函数接收列表作为参数,它可以使用列表的尾部调用自身,而不必牺牲整体,因为尾部是原始列表的子结构,因此更接近Nil
)。 (n-1)
和(n-2)
这样的表达式在Int
类型时的问题是,虽然它们数字小于n,但它们不是结构更小,因为Int
没有归纳定义,因此没有基本情况。因此,整体检查器无法确定递归总是最终会达到基本情况(即使对我们来说似乎很明显),因此它不会认为fibo
是完全的。
首先,让我们解决递归问题。我们可以使用归纳定义的数据类型,而不是Int
,而不是Nat
:
data Nat =
Z | S Nat
(自然数为零,或另一个自然数的后继。)
这允许我们将fibo
重写为:
fibo : Nat -> Int
fibo (S Z) = 1
fibo (S (S Z)) = 2
fibo (S (S n)) = fibo (S n) + fibo n
(注意在递归的情况下,我们不是明确地计算(n-1)
和(n-2)
,而是通过参数上的模式匹配来生成它们,从而向Idris证明它们在结构上更小。)
这个fibo
的新定义仍然不完全是完全的,因为它没有Z
的情况(即零)。如果我们不想提供这样的案例,那么我们需要给伊德里斯一些保证,不允许它发生。我们可以这样做的一种方法是要求证明fibo
的参数大于或等于1(或等效地,一个小于或等于参数):
fibo : (n : Nat) -> LTE 1 n -> Int
fibo Z LTEZero impossible
fibo Z (LTESucc _) impossible
fibo (S Z) _ = 1
fibo (S (S Z)) _ = 2
fibo (S (S (S n))) _ = fibo (S (S n)) (LTESucc LTEZero) + fibo (S n) (LTESucc LTEZero)
LTE 1 n
是其值为1≤ n (在自然数内)的证明的类型。 LTEZero
表示零≤任何自然数的公理,LTESucc
表示如果n≤m,则( n 的后继者)的规则≤( m 的后继者)。 impossible
关键字表示不能发生给定的案例。在上面的定义中,fibo
的第一个参数不可能为零,因为没有办法证明1≤0。对于任何其他自然数n,我们可以证明1≤ n 使用(LTESucc LTEZero)
。
现在最后fibo
是完全的,但是必须为它提供一个明确证明其参数大于或等于1的相当麻烦。幸运的是,我们可以将证明参数标记为自动隐式:
fibo : (n : Nat) -> {auto p : LTE 1 n} -> Int
fibo Z {p = LTEZero} impossible
fibo Z {p = (LTESucc _)} impossible
fibo (S Z) = 1
fibo (S (S Z)) = 2
fibo (S (S (S n))) = fibo (S (S n)) + fibo (S n)
Idris现在会自动找到1≤ n 的证据,否则我们仍然需要自己提供证明。
*我可能会在没有意识到的情况下掩饰一些与字塔相关的微妙之处,但这是一个广泛的原则。