Для целей этого ответа я предполагаю, что определение fold
является чем-то вроде
Fixpoint fold {A B: Type} (f: A -> B -> B) (u: list A) (b: B): B :=
match u with
| [] => b
| x :: v => f x (fold f v b)
end.
(в основном fold_right
из стандартной библиотеки). Если ваше определение существенно отличается, рекомендованная мною тактика может не сработать.
Проблема здесь заключается в поведении simpl
с константами, которые необходимо развернуть, прежде чем их можно упростить. С документация :
Обратите внимание, что упрощенные могут разворачиваться только прозрачные константы, чье имя может быть повторно использовано в рекурсивных вызовах. Например, константа, определенная с помощью plus ': = plus, может быть развернута и повторно использована в рекурсивных вызовах, но такая константа, как succ: = plus (S O), никогда не раскрывается.
Это немного сложно понять, поэтому давайте рассмотрим пример.
Definition add_5 (n: nat) := n + 5.
Goal forall n: nat, add_5 (S n) = S (add_5 n).
Proof.
intro n.
simpl.
unfold add_5; simpl.
exact eq_refl.
Qed.
Вы увидите, что первый вызов simpl
ничего не сделал, хотя add_5 (S n)
можно было бы упростить до S (n + 5)
. Однако, если я unfold add_5
первый, он работает отлично. Я думаю, что проблема в том, что plus_5
не является напрямую Fixpoint
. Хотя plus_5 (S n)
эквивалентно S (plus_5 n)
, на самом деле это не его определение. Так что Coq не признает, что его «имя может быть повторно использовано в рекурсивных вызовах». Nat.add
(то есть "+") определяется непосредственно как рекурсивный Fixpoint
, поэтому simpl
упрощает его.
Поведение simpl
можно немного изменить (см. Документацию снова). Как упоминает Антон в комментариях, вы можете использовать народную команду Arguments
для изменения, когда simpl
пытается упростить. Arguments fold_length _ _ /.
сообщает Coq, что fold_length
следует развернуть, если указано как минимум два аргумента (наклонная черта разделяет обязательные аргументы слева и ненужные аргументы справа). [Sup] 1 [\ sup]
Более простая тактика, если вы не хотите иметь с ней дело, это cbn
, которая работает здесь по умолчанию и работает лучше в целом. Цитирование из документации :
Тактика cbn считается более принципиальной, быстрой и предсказуемой заменой упрощенной.
Ни simpl
с Arguments
и косой чертой, ни cbn
не уменьшают цель до того, что вы хотите в вашем случае, так как она развернется fold_length
, но не перевернет ее. Вы можете распознать, что вызов fold
- это всего лишь fold_length l
, и перекомпилировать его с fold (fold_length l)
.
Другая возможность в вашем случае - использовать тактику change
. Казалось, вы уже знали, что fold_length (a :: l)
должен был упростить до S (fold_length l)
. Если это так, вы можете использовать change (fold_length (a :: l)) with (S (fold_length l)).
, и Coq попытается преобразовать одно в другое (используя только основные правила преобразования, а не равенства, как rewrite
делает).
После того, как вы достигли цели S (fold_length l) = S (length l)
, используя любую из вышеперечисленных тактик, вы можете использовать rewrite -> IHl.
, как вы и хотели.
- Я думал, что косые черты только
simpl
раскрывают вещи меньше , поэтому я не упоминал об этом раньше. Я не уверен, какое значение по умолчанию на самом деле, так как при расположении косой черты в любом месте simpl
раскрывается fold_length
.