Idrisで証明がしやすい書き方

κeenです。なんか証明しづらいなって思ってたところをコード側を書き直したらすんなりいけたのでメモ。

元のコードは以下。何をするコードかはあまり気にしなくてよい。

export
dequeue : BoundedQueue n a -> (Maybe a, BoundedQueue n a)
dequeue q@(MkQueue [] [] pad) = (Nothing, q)
dequeue {n=S l + m + pad} (MkQueue {l=S l} {m} (x :: xs) rear pad) =
  rewrite plusSuccRightSucc (l + m) pad in
  (Just x, MkQueue xs rear (S pad))
dequeue {n = S m + pad} (MkQueue {l=Z} {m=S m} [] rear pad) =
  case reverse rear of
    e::front =>
      rewrite plusSuccRightSucc m pad in
      rewrite sym $ plusZeroRightNeutral m in
      (Just e, MkQueue front [] (S pad))

コードの形を見ると以下のような外形をしている。

fun ... .. =
    case ... of
     ... => rewrite .. in
            (Just ..., MkQueue ...)

fun の本体部分が1つの式になっていて、全体を caserewrite が包んでいる。 これだと証明するときに caserewrite で止まって簡約が進まないのでお手上げになる。

そこで let をうまく使って最後の式を caserewrite で包まないようにした。

export
dequeue : BoundedQueue n a -> (Maybe a, BoundedQueue n a)
dequeue q@(MkQueue [] [] pad) = (Nothing, q)
dequeue {n=S l + m + pad} (MkQueue {l=S l} {m} (x :: xs) rear pad) =
  let q: BoundedQueue (l + m + (S pad)) a = MkQueue xs rear (S pad) in
  let q: BoundedQueue (S (l + m + pad)) a = rewrite plusSuccRightSucc (l + m) pad in q in
  (Just x, q)
dequeue {n = S m + pad} (MkQueue {l=Z} {m=S m} [] rear pad) =
  let rear' = reverse rear in
  let e = head rear' in
  let front = tail rear' in
  let q: BoundedQueue (m + Z + S pad) a = MkQueue front [] (S pad) in
  let q: BoundedQueue (m +     S pad) a = rewrite sym $ plusZeroRightNeutral m in q in
  let q: BoundedQueue (S (m + pad))   a = rewrite plusSuccRightSucc m pad in q in
  (Just e, q)

let は一旦後回しにできるからか簡約が進むらしく、証明するときに (Just e, q) まで進んでいた。これならコンストラクタが露出しているので続く証明がやりやすい。

へー、便利ーと思う反面ちょっとしたコードの書き方で証明できる/できないが変わるのは地獄だよねー。

Written by κeen