为什么顺序在函数定义中很重要?

时间:2018-07-18 13:26:09

标签: pattern-matching agda

给出列表的这些定义:

data List (A : Set) : Set where
    []   : List A
    _::_ : (x : A)(xs : List A) → List A

length : {A : Set} → List A → ℕ
length []        = zero
length (_ :: xs) = suc (length xs)

lookup : {A : Set}(xs : List A)(n : ℕ) → (isTrue (n < length xs)) → A
lookup [] n ()
lookup (x :: xs) zero p = x
lookup (x :: xs) (suc n) p = lookup xs n p

和布尔值:

data Bool : Set where
    true  : Bool
    false : Bool

data False : Set where
record True : Set where

isTrue : Bool → Set
isTrue true     = True
isTrue false    = False

_<_定义为:

时,此编译没有错误。
open import Data.Nat using (ℕ; suc; zero)

_<_ : ℕ → ℕ → Bool
_     < zero   = false
zero  < suc n  = true
suc m < suc n = m < n

但是如果我将前两个定义的顺序切换为:

_<_ : ℕ → ℕ → Bool
zero  < suc n  = true
_     < zero   = false
suc m < suc n = m < n

然后我得到了错误:

isTrue (n < length []) should be empty, but that's not obvious to me
when checking that the clause lookup [] n () has type
{A : Set} (xs : List A) (n : ℕ) → isTrue (n < length xs) → A

这向我表明n < length []zero < suc n = true匹配,这不应该,因为length []等于zerosuc m < suc n = m < n,它不应该匹配不会这样做,因为这是_<_的最后一个定义(其他情况也是如此)。

我误解了agda如何与函数定义匹配,或者荒谬的模式如何工作?

2 个答案:

答案 0 :(得分:3)

在内部,Agda通过将模式匹配转换为案例树来转换定义(请参见ReadTheDocs中的文档)。为了构造此案例树,Agda始终首先查看第一个子句。因此,根据条款的顺序,结果案例树可能会有所不同。在您的示例中,_<_的第一个定义的案例树是(用伪语法):

m < n = case n of
          zero   -> false
          suc n' -> case m of
            zero   -> true
            suc m' -> m' < n'

_<_的第二个定义的案例树为

m < n = case m of
          zero   -> case n of
            zero   -> false
            suc n' -> true
          suc m' -> case n of
            zero   -> false
            suc m' -> m' < n'

实际上,在第二个定义中,Agda将单个子句_ < zero = false分为两个子句zero < zero = falsesuc m < zero = false,从而阻止n < zero进一步缩小。

要阻止Agda像这样对子句进行拆分,可以将{-# OPTIONS --exact-split #-}放在文件顶部(或将--exact-split添加到Agda的命令行标志中)。

如果您想了解有关如何将模式匹配的定义详细化为案例树的更多信息,可以阅读关于主题的our upcoming ICFP paper

答案 1 :(得分:2)

Jesper的正确答复还指出了使lookup_<_的第二个定义一起工作的另一种方法:只需自己匹配n即可清楚地知道{{1 }}:

_<_