将输入参数约束到函数

时间:2016-06-30 22:44:05

标签: idris

假设我想将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或更大的整数值?

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 的证据,否则我们仍然需要自己提供证明。

*我可能会在没有意识到的情况下掩饰一些与字塔相关的微妙之处,但这是一个广泛的原则。