【问题标题】:Non-tedious AST transformation proofs in AgdaAgda 中非繁琐的 AST 转换证明
【发布时间】:2014-06-29 11:14:09
【问题描述】:

我在 Software Foundations 的 "simple imperative programs" 章节工作,一路上也使用 Agda 进行练习。该书指出,在 AST-s 上进行证明是乏味的,并继续介绍 Coq 中的自动化工具。

如何减少 Agda 的乏味?

这是一个示例代码:

open import Data.Nat hiding (_≤?_)
open import Function
open import Data.Bool
open import Relation.Binary.PropositionalEquality
open import Data.Empty
open import Data.Product
open import Data.Unit hiding (_≤?_)

data AExp : Set where
  ANum : ℕ → AExp
  APlus AMinus AMult : AExp → AExp → AExp

aeval : AExp → ℕ
aeval (ANum x) = x
aeval (APlus a b) = aeval a + aeval b 
aeval (AMinus a b) = aeval a ∸ aeval b  
aeval (AMult a b) = aeval a * aeval b

opt-0+ : AExp → AExp
opt-0+ (ANum x) = ANum x
opt-0+ (APlus (ANum 0) b) = b
opt-0+ (APlus a b) = APlus (opt-0+ a) (opt-0+ b)
opt-0+ (AMinus a b) = AMinus (opt-0+ a) (opt-0+ b)
opt-0+ (AMult a b) = AMult (opt-0+ a) (opt-0+ b)

opt-0+-sound : ∀ e → aeval (opt-0+ e) ≡ aeval e
opt-0+-sound (ANum x) = refl
opt-0+-sound (APlus (ANum zero) b) rewrite opt-0+-sound b = refl
opt-0+-sound (APlus (ANum (suc x)) b) rewrite opt-0+-sound b = refl
opt-0+-sound (APlus (APlus a a₁) b) rewrite opt-0+-sound (APlus a a₁) | opt-0+-sound b = refl
opt-0+-sound (APlus (AMinus a a₁) b) rewrite opt-0+-sound (AMinus a a₁) | opt-0+-sound b = refl
opt-0+-sound (APlus (AMult a a₁) b) rewrite opt-0+-sound (AMult a a₁) | opt-0+-sound b = refl
opt-0+-sound (AMinus a b) rewrite opt-0+-sound a | opt-0+-sound b = refl
opt-0+-sound (AMult a b) rewrite opt-0+-sound a | opt-0+-sound b = refl

这里的一些具体问题:

首先,如果我用普通的 Haskell 编写一个未经验证的程序,我会排除术语递归或使用泛型编程。我也可以在 Agda 中编写一个通用的转换函数:

transform : (AExp → AExp) → AExp → AExp
transform f (ANum x)     = f (ANum x)
transform f (APlus a b)  = f (APlus  (transform f a) (transform f b))
transform f (AMinus a b) = f (AMinus (transform f a) (transform f b))
transform f (AMult a b)  = f (AMult  (transform f a) (transform f b))

opt-0+ : AExp → AExp
opt-0+ = transform (λ {(APlus (ANum 0) b) → b; x → x})

但随后证明变得可怕。我还尝试定义一个标准的变态,然后根据它定义评估和转换,然后尝试根据作为变态参数的函数(对应于构造函数)来证明事情,但我几乎失败了这种方法。所以,在这里我想知道是否有一种可行的“通用”方法来证明写作,它只关注相关案例而跳过其他案例。

其次,Agda 在展开函数定义时没有考虑“catch all”模式。回忆一下我的证明中的这一部分:

opt-0+-sound (APlus (ANum zero) b) rewrite opt-0+-sound b = refl
opt-0+-sound (APlus (ANum (suc x)) b) rewrite opt-0+-sound b = refl
opt-0+-sound (APlus (APlus a a₁) b) rewrite opt-0+-sound (APlus a a₁) | opt-0+-sound b = refl
opt-0+-sound (APlus (AMinus a a₁) b) rewrite opt-0+-sound (AMinus a a₁) | opt-0+-sound b = refl
opt-0+-sound (APlus (AMult a a₁) b) rewrite opt-0+-sound (AMult a a₁) | opt-0+-sound b = refl

在第一行以下的所有情况下,Agda 不记得我们已经涵盖了与opt-0+ 唯一相关的情况,因此我们必须再次写出每个构造函数。随着构造函数数量的增加,这个问题变得更加令人讨厌。 是否有消除样板案例的技巧?

【问题讨论】:

    标签: agda


    【解决方案1】:

    让我们概括一下您的transform

    foldAExp : {A : Set} -> (ℕ -> A) -> (_ _ _ : A -> A -> A) -> AExp -> A
    foldAExp f0 f1 f2 f3 (ANum x)     = f0 x
    foldAExp f0 f1 f2 f3 (APlus a b)  = f1 (foldAExp f0 f1 f2 f3 a) (foldAExp f0 f1 f2 f3 b)
    foldAExp f0 f1 f2 f3 (AMinus a b) = f2 (foldAExp f0 f1 f2 f3 a) (foldAExp f0 f1 f2 f3 b)
    foldAExp f0 f1 f2 f3 (AMult a b)  = f3 (foldAExp f0 f1 f2 f3 a) (foldAExp f0 f1 f2 f3 b)
    

    现在我们可以编写这个函数了:

    generalize : ∀ f0 f1 f2 f3
               -> (∀ x   -> aeval (f0 x)   ≡ aeval (ANum x))
               -> (∀ a b -> aeval (f1 a b) ≡ aeval (APlus a b))
               -> (∀ a b -> aeval (f2 a b) ≡ aeval (AMinus a b))
               -> (∀ a b -> aeval (f3 a b) ≡ aeval (AMult a b))
               -> (∀ e -> aeval (foldAExp f0 f1 f2 f3 e) ≡ aeval e)
    generalize f0 f1 f2 f3 p0 p1 p2 p3 (ANum x) = p0 x
    generalize f0 f1 f2 f3 p0 p1 p2 p3 (APlus a b)
      rewrite p1 (foldAExp f0 f1 f2 f3 a) (foldAExp f0 f1 f2 f3 b)
      | generalize f0 f1 f2 f3 p0 p1 p2 p3 a | generalize f0 f1 f2 f3 p0 p1 p2 p3 b = refl
    generalize f0 f1 f2 f3 p0 p1 p2 p3 (AMinus a b)
      rewrite p2 (foldAExp f0 f1 f2 f3 a) (foldAExp f0 f1 f2 f3 b)
      | generalize f0 f1 f2 f3 p0 p1 p2 p3 a | generalize f0 f1 f2 f3 p0 p1 p2 p3 b = refl
    generalize f0 f1 f2 f3 p0 p1 p2 p3 (AMult a b)
      rewrite p3 (foldAExp f0 f1 f2 f3 a) (foldAExp f0 f1 f2 f3 b)
      | generalize f0 f1 f2 f3 p0 p1 p2 p3 a | generalize f0 f1 f2 f3 p0 p1 p2 p3 b = refl
    

    因此,如果我们有这样的函数 f0f1f2f3,它们不会改变任何适当子表达式的“含义”(Num _ for f0APlus _ _对于f1 等),我们可以使用这些函数折叠任何表达式而不改变其“含义”。这是一个简单的例子:

    idAExp : AExp → AExp
    idAExp = foldAExp ANum APlus AMinus AMult
    
    idAExp-sound : ∀ e → aeval (idAExp e) ≡ aeval e
    idAExp-sound = generalize _ _ _ _ (λ _ → refl) (λ _ _ → refl) (λ _ _ → refl) (λ _ _ → refl)
    

    现在我们需要可判定的平等机制来“记住”涵盖的案例。我将在下面发布整个代码的链接,因为有很多样板。这是引理,你想证明:

    0+-f1 : AExp -> AExp -> AExp
    0+-f1 a         b with a ≟AExp ANum 0
    0+-f1 .(ANum 0) b | yes refl = b
    0+-f1  a        b | no  p    = APlus a b
    
    opt-0+ : AExp → AExp
    opt-0+ = foldAExp ANum 0+-f1 AMinus AMult
    
    0+-p1 : ∀ a b -> aeval (0+-f1 a b) ≡ aeval (APlus a b)
    0+-p1  a        b with a ≟AExp ANum 0
    0+-p1 .(ANum 0) b | yes refl = refl
    0+-p1  a        b | no  p    = refl
    
    opt-0+-sound : ∀ e → aeval (opt-0+ e) ≡ aeval e
    opt-0+-sound = generalize _ _ _ _ (λ _ → refl) 0+-p1 (λ _ _ → refl) (λ _ _ → refl)
    

    让我们证明更花哨的引理。

    fancy-lem : ∀ a1 a2 b1 b2 -> a1 * b1 + a1 * b2 + a2 * b1 + a2 *  b2 ≡ (a1 + a2) * (b1 + b2)
    fancy-lem = solve
      4
      (λ a1 a2 b1 b2 → a1 :* b1 :+ a1 :* b2 :+ a2 :* b1 :+ a2 :* b2 := (a1 :+ a2) :* (b1 :+ b2))
      refl
        where
          import Data.Nat.Properties
          open Data.Nat.Properties.SemiringSolver
    

    现在我们要对 AExp 术语进行这样的优化:

    left : AExp -> AExp
    left (ANum   x  ) = ANum x
    left (APlus  a b) = a
    left (AMinus a b) = a
    left (AMult  a b) = a
    
    right : AExp -> AExp
    right (ANum x    ) = ANum x
    right (APlus a b ) = b
    right (AMinus a b) = b
    right (AMult  a b) = b
    
    fancy-f3 : AExp -> AExp -> AExp
    fancy-f3 a b with left a | right a | left b | right b
    fancy-f3 a b | a1 | a2 | b1 | b2 with a ≟AExp APlus a1 a2 | b ≟AExp APlus b1 b2
    fancy-f3 .(APlus a1 a2) .(APlus b1 b2) | a1 | a2 | b1 | b2 | yes refl | yes refl =
      APlus (APlus (APlus (AMult a1 b1) (AMult a1 b2)) (AMult a2 b1)) (AMult a2 b2)
    fancy-f3  a              b             | a1 | a2 | b1 | b2 | _        | _        = AMult a 
    
    opt-fancy : AExp → AExp
    opt-fancy = foldAExp ANum APlus AMinus fancy-f3
    

    以及健全性证明:

    fancy-p3 : ∀ a b -> aeval (fancy-f3 a b) ≡ aeval (AMult a b)
    fancy-p3 a b with left a | right a | left b | right b
    fancy-p3 a b | a1 | a2 | b1 | b2 with a ≟AExp APlus a1 a2 | b ≟AExp APlus b1 b2
    fancy-p3 .(APlus a1 a2) .(APlus b1 b2) | a1 | a2 | b1 | b2 | yes refl | yes refl =
      fancy-lem (aeval a1) (aeval a2) (aeval b1) (aeval b2)
    fancy-p3 .(APlus a1 a2)  b             | a1 | a2 | b1 | b2 | yes refl | no  _    = refl
    fancy-p3  a             .(APlus b1 b2) | a1 | a2 | b1 | b2 | no  _    | yes refl = refl
    fancy-p3  a              b             | a1 | a2 | b1 | b2 | no  _    | no  _    = refl
    
    opt-fancy-sound : ∀ e → aeval (opt-fancy e) ≡ aeval e
    opt-fancy-sound = generalize _ _ _ _ (λ _ → refl) (λ _ _ → refl) (λ _ _ → refl) fancy-p3
    

    这里是完整的代码:http://lpaste.net/106481 可以减少 generalize≟AExp 中的样板数量。技巧在这里描述:http://rubrication.blogspot.ru/2012/03/decidable-equality-in-agda.html 抱歉,如果显示的东西很傻,我的浏览器就疯了。

    编辑:

    不需要乱七八糟的foldAExp 东西。通常的transform 让事情变得更容易。以下是一些定义:

    transform : (AExp → AExp) → AExp → AExp
    transform f (ANum x)     = f (ANum x)
    transform f (APlus a b)  = f (APlus  (transform f a) (transform f b))
    transform f (AMinus a b) = f (AMinus (transform f a) (transform f b))
    transform f (AMult a b)  = f (AMult  (transform f a) (transform f b))
    
    generalize : ∀ f -> (∀ e -> aeval (f e) ≡ aeval e)
               -> (∀ e -> aeval (transform f e) ≡ aeval e)
    generalize f p (ANum x)    = p (ANum x)
    generalize f p (APlus a b)  rewrite p (APlus  (transform f a) (transform f b))
      | generalize f p a | generalize f p b = refl
    generalize f p (AMinus a b) rewrite p (AMinus (transform f a) (transform f b))
      | generalize f p a | generalize f p b = refl
    generalize f p (AMult a b)  rewrite p (AMult  (transform f a) (transform f b))
      | generalize f p a | generalize f p b = refl
    
    idAExp : AExp → AExp
    idAExp = transform id
    
    idAExp-sound : ∀ e → aeval (idAExp e) ≡ aeval e
    idAExp-sound = generalize _ (λ _ → refl)
    

    以及整个代码:http://lpaste.net/106500

    【讨论】:

    • 谢谢,这是值得思考的好东西。
    • 我删除了一些乱七八糟的东西,见EDIT
    【解决方案2】:

    由于我们不需要no 案例的证明,因此切换到这种数据类型可能会更好:

    data Dec' {p} (P : Set p) : Set p where
      yes : (p : P) → Dec' P
      no  : Dec' P
    

    因为有n * (n - 1)no 案例和n yes 案例。所以这种表示是相当可扩展的。

    还可以使所有这些可判定性自动工作。 这是转换的主要功能:

    vecApply : {α γ : Level} {X : Set α} {Z : Set γ} -> (n : ℕ) -> nary n X Z -> Vec X n -> Z
    vecApply  0      x  _       = x
    vecApply (suc n) f (x ∷ xs) = vecApply n (f x) xs
    
    replace' : (n : ℕ) -> nary n AExp (AExp × AExp) -> AExp -> AExp
    replace' n f e with getSubterms n f e
    replace' n f e | nothing = e
    replace' n f e | just xs with vecApply n f xs
    replace' n f e | just xs |  e' , e'' with e ≟AExp e'
    replace' n f e | just xs | .e  , e'' | yes refl = e''
    replace' n f e | just xs |  e' , e'' | no       = e
    

    所以你提供了一些函数,它接收n 参数并返回两个表达式。例如:

    _==_ : {α β : Level} {A : Set α} {B : Set β} -> A -> B -> A × B
    _==_ = _,_
    
    0+-func : AExp -> AExp × AExp
    0+-func = λ a2 -> APlus (ANum 0) a2 == a2
    

    第一个表达式是您要查找的内容,第二个表达式用于替换第一个表达式。首先,您需要编写一个函数来查找所有适当的子表达式。例如

    ex1-func : (_ _ : AExp) -> AExp × AExp
    ex1-func = λ a1 b1 -> AMult (APlus a1 b1) (APlus a1 b1) == ANum 0
    

    对于ex1-func 和这个词

    let    a1 = ANum 0
    in let b1 = ANum 1
    in AMult (APlus a1 b1) (APlus a1 b1)
    

    这个函数应该按照这个顺序返回ANum 0 ∷ ANum 1 ∷ []。 要首先实现这一点,您需要确定某个表达式中的所有“漏洞”(上例中的a1b1)。然后你需要删除重复项(a1 有两个“洞”,而ex1-func(和任何其他函数)两个洞只接收一个a1)。

    这是一个肮脏的解决方案:

    enlarge : AExp -> AExp
    enlarge a = APlus a a
    
    size : AExp -> ℕ
    size (APlus a _) = 1 + size a
    size  _          = 0
    
    small big : AExp
    small = ANum 0
    big   = enlarge small
    
    transT : Set
    transT = AExp -> AExp
    
    transTs : Set
    transTs = L.List transT
    
    left : transT
    left (ANum   x  ) = ANum x
    left (APlus  a b) = a
    left (AMinus a b) = a
    left (AMult  a b) = a
    
    right : transT
    right (ANum   x  ) = ANum x
    right (APlus  a b) = b
    right (AMinus a b) = b
    right (AMult  a b) = b
    
    directions : AExp -> AExp -> transTs
    directions (ANum   _)     (ANum   _)     = L.[]
    directions (APlus  a1 a2) (APlus  b1 b2) =
      L.map (λ f -> f ∘ left) (directions a1 b1) L.++ L.map (λ f -> f ∘ right) (directions a2 b2)
    directions (AMinus a1 a2) (AMinus b1 b2) =
      L.map (λ f -> f ∘ left) (directions a1 b1) L.++ L.map (λ f -> f ∘ right) (directions a2 b2)
    directions (AMult  a1 a2) (AMult  b1 b2) =
      L.map (λ f -> f ∘ left) (directions a1 b1) L.++ L.map (λ f -> f ∘ right) (directions a2 b2)
    directions  _              _             = id L.∷ L.[]
    
    add : {l : ℕ} -> ℕ -> transT -> Vec transTs l -> Vec transTs l  
    add  _      d  []      = []
    add  0      d (x ∷ xs) = (d L.∷ x) ∷ xs
    add (suc n) d (x ∷ xs) = x ∷ add n d xs
    
    naryApply : {α γ : Level} {X : Set α} {Z : Set γ} -> (n : ℕ) -> nary n X Z -> X -> Z
    naryApply  0      x _ = x
    naryApply (suc n) f x = naryApply n (f x) x
    
    naryApplyWith : {α γ : Level} {X : Set α} {Z : Set γ}
                  -> (n : ℕ) -> nary n X Z -> (X -> X) -> X -> Z
    naryApplyWith  0      x _ _ = x
    naryApplyWith (suc n) f g x = naryApplyWith n (f x) g (g x)
    
    directionses : (n : ℕ) -> nary n AExp (AExp × AExp) -> Vec transTs n
    directionses n f = L.foldr (λ f -> add (size (f e)) f) (replicate L.[]) $
      directions (proj₁ $ naryApply n f big) (proj₁ $ naryApply n f small) where
        e = proj₁ $ naryApplyWith n f enlarge small
    
    open RawMonad {{...}}
    
    getSubterms : (n : ℕ) -> nary n AExp (AExp × AExp) -> AExp -> Maybe (Vec AExp n)
    getSubterms n f e = (λ _ -> map (λ fs -> lhead id fs e) dss) <$> flip (mapM M.monad) dss
      (L.sequence M.monad ∘ neighbWith (λ f g -> dec'ToMaybe⊤ $ f e ≟AExp g e)) where
        dss = directionses n f
    

    我们的想法是将您的函数应用于两个不同的术语,然后找到不同之处。这里的“差异”是一个函数列表,如left ∘ right ∘ right(这很脏,但我想可以改进)。现在您可以导航了。然后你再次应用这个函数,但现在每个词都比以前大,所以可以区分它们(这就是 size 函数所做的)。最后,此函数检查是否所有相同的孔都被相同的表达式填充。如果是这样,它会在每个“相同的家庭”中选择随机(实际上是第一个)表达式并将它们收集到一个向量中。

    replace' 函数中的其他内容非常简单。将变换函数应用于子表达式向量,并将最终项与原始项进行比较。如果它们是相同的,那么你找到了一个子表达式,它可以像转换函数所说的那样被转换。

    现在您需要从一个子项转到所有子项:

    replace : (n : ℕ) -> nary n AExp (AExp × AExp) -> AExp -> AExp 
    replace n f = transform (replace' n f)
    

    这就是改造的全部。证明东西是相当对称的。

    sound' : ∀ n f
           -> soundnessProof n f
           -> ∀ e -> aeval (replace' n f e) ≡ aeval e
    sound' n f p e with getSubterms n f e
    sound' n f p e | nothing = refl
    sound' n f p e | just xs with vecApply n f xs | vecApplyProof p xs
    sound' n f p e | just xs |  e' , e'' | p' with e ≟AExp e'
    sound' n f p e | just xs | .e  , e'' | p' | yes refl = p'
    sound' n f p e | just xs |  e' , e'' | p' | no       = refl
    

    唯一的区别——sound' 收到了你的转换函数的健全性证明。

    soundnessProof : (n : ℕ) -> nary n AExp (AExp × AExp) -> Set 
    soundnessProof  0      (e' , e'') = aeval e'' ≡ aeval e'
    soundnessProof (suc n)     f      = ∀ x -> soundnessProof n (f x)
    

    这表示,对于所有参数f 必须返回具有相同“含义”的两个术语的元组。回想一下这个例子:

    _==_ : {α β : Level} {A : Set α} {B : Set β} -> A -> B -> A × B
    _==_ = _,_
    
    0+-func : AExp -> AExp × AExp
    0+-func = λ a2 -> APlus (ANum 0) a2 == a2
    

    vecApplyProof 在值级别是对称的,但在类型级别稍微复杂一些:

    vecApplyProof : {n : ℕ} {f : nary n AExp (AExp × AExp)}
                   -> soundnessProof n f -> (xs : Vec AExp n)
                   -> uncurry (λ p1 p2 -> aeval p2 ≡ aeval p1) $ vecApply n f xs
    vecApplyProof {0}     p  _       = p
    vecApplyProof {suc n} p (x ∷ xs) = vecApplyProof {n} (p x) xs
    

    而且你还需要从一个子表达式移动到所有子表达式:

    generalize : ∀ f -> (∀ e -> aeval (f e) ≡ aeval e)
               -> (∀ e -> aeval (transform f e) ≡ aeval e)
    generalize f p (ANum x)    = p (ANum x)
    generalize f p (APlus a b)  rewrite p (APlus  (transform f a) (transform f b))
      | generalize f p a | generalize f p b = refl
    generalize f p (AMinus a b) rewrite p (AMinus (transform f a) (transform f b))
      | generalize f p a | generalize f p b = refl
    generalize f p (AMult a b)  rewrite p (AMult  (transform f a) (transform f b))
      | generalize f p a | generalize f p b = refl
    
    sound : (n : ℕ) -> (f : nary n AExp (AExp × AExp))
          -> soundnessProof n f
          -> (∀ e -> aeval (replace n f e) ≡ aeval e)
    sound n f p = generalize _ (sound' n f p)
    

    最后一个例子:

    fancy-func : (_ _ _ _ : AExp) -> AExp × AExp
    fancy-func = λ a1 a2 b1 b2 -> AMult (APlus a1 a2) (APlus b1 b2) ==
      APlus (APlus (APlus (AMult a1 b1) (AMult a1 b2)) (AMult a2 b1)) (AMult a2 b2)
    
    opt-fancy : AExp → AExp
    opt-fancy = replace 4 fancy-func
    
    test-opt-fancy :
      let    a1 = ANum 0
      in let a2 = AMinus a1 a1
      in let b1 = ANum 1
      in let b2 = AMinus b1 b1
      in opt-fancy (AMinus (AMult (APlus a1 a2) (APlus b1 b2)) (ANum 0)) ≡
        (AMinus (APlus (APlus (APlus (AMult a1 b1) (AMult a1 b2)) (AMult a2 b1)) (AMult a2 b2)) (ANum 0)) 
    test-opt-fancy = refl
    
    fancy-lem : ∀ a1 a2 b1 b2 -> a1 * b1 + a1 * b2 + a2 * b1 + a2 *  b2 ≡ (a1 + a2) * (b1 + b2)
    fancy-lem = solve
      4
      (λ a1 a2 b1 b2 → a1 :* b1 :+ a1 :* b2 :+ a2 :* b1 :+ a2 :* b2 := (a1 :+ a2) :* (b1 :+ b2))
      refl
        where
          import Data.Nat.Properties
          open Data.Nat.Properties.SemiringSolver
    
    opt-fancy-sound : ∀ e → aeval (opt-fancy e) ≡ aeval e
    opt-fancy-sound = sound 4 fancy-func
      (λ a1 a2 b1 b2 -> fancy-lem (aeval a1) (aeval a2) (aeval b1) (aeval b2))
    

    整个故事:http://lpaste.net/106670

    编辑:directions 函数中的组合策略错误(例如,_∘_ left 而不是λ f -&gt; f ∘ left)。现已修复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多