Почему определение «половины» в Fixpoint для списка не работает?

Я пытаюсь определить функцию Fixpoint для list X, чтобы получить первую половину подсписка, если это нечетное количество элементов, то средний элемент не учитывается.

Вот что я пробовал:

Fixpoint half {X : Type} (l : list X) : list X :=
  match l with
  | [] => []
  | h :: t => match (rev t) with
              | [] => []
              | p :: b => [h] ++ (half (rev b))
              end
  end.

Но выдает ошибку:
Cannot guess decreasing argument of fix.

Что мне здесь не хватает?

rev b структурно не меньше l. См. также: stackoverflow.com/questions/46348606/…
Naïm Favier 10.08.2024 14:02

Что вы подразумеваете под structurally smaller? rev b является частью t, поэтому я не могу понять, почему он не меньше l... Ссылка кажется гораздо сложнее, попробую переварить, спасибо! @НаимФавиер

Tyl 10.08.2024 14:15

Вам придется сообщить Coq и использовать тот факт, что rev b является подсписком l и строго меньше l, о чем Coq не может просто догадаться. А способы «сообщить Coq об этом» — это то, что вы можете увидеть в теме, на которую ссылается @NaïmFavier. OTOH, я бы не хотел давать решение по этому вопросу, если вы прямо не спросите, учитывая, что ваш код крайне неэффективен, его не просто сложно писать и использовать, хотя я могу придумать хотя бы один способ реализации half, который чрезвычайно проще и, возможно, даже наиболее эффективно (для простого связанного списка): использование length и firstn...

Julio Di Egidio 10.08.2024 14:53

Я согласен, что это неудобно и неэффективно. На самом деле я пытался написать это для целей доказательства. Мне просто интересно узнать причину. Спасибо за подсказки :-) @JulioDiEgidio

Tyl 10.08.2024 16:21

(На самом деле, для n длины списка существует решение за O(n), но ценой необходимости использования вспомогательной функции: в любом случае, я не буду на этом останавливаться.)

Julio Di Egidio 10.08.2024 18:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
76
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете выполнить рекурсивный вызов только в том случае, если один из ваших аргументов является «обоснованным» индуктивным типом данных. Coq должен иметь возможность это проверить, и один из способов — увидеть, что рекурсивный аргумент является небольшой частью исходного аргумента. Вы не можете просто применить к нему функцию, так как это может сделать аргумент «больше», и тогда невозможно будет так же легко доказать, что рекурсия завершится. В вашем случае coq не видит, что rev b, например, не возвращает более длинный список. В этом случае мы можем выбрать что-то еще, что, как мы знаем, монотонно уменьшается, и использовать это, чтобы показать, что рекурсия завершается. Например, длина входного аргумента. Но тогда нам придется проделать кое-какую работу, чтобы показать это Коку.

Если вы хотите использовать более гибкий способ указания рекурсии, импортируйте модуль Program и используйте Program Fixpoint вместо Fixpoint, чтобы определить свою функцию. И добавьте аргумент в {}, указывающий монотонно убывающую меру.

Например, мы можем использовать {measure (length l)}, чтобы указать, что длина аргумента l уменьшается при рекурсивных вызовах.

Затем определите функцию и используйте Next Obligation., чтобы получить оставшиеся обязательства по доказательству.

Require Import List. Import ListNotations.
Require Import Program.

(* add Program  and {measure ...} *)

Program Fixpoint half {X : Type} (l : list X) {measure (length l)} : list X :=
  match l with
  | [] => []
  | h :: t => match (rev t) with
              | [] => []
              | p :: b => [h] ++ (half (rev b))
              end
  end
  .
  
Next Obligation.
(* Here we need to prove that the length of 
   the argument to the recursive call is smaller than 
   that of the calling function's argument: 

  Heq_anonymous : p :: b = rev t 
  --------
  length (rev b) < length (h :: t) 
*)

(*  We need to find some lemma that shows that length (rev x) = length x
Search (length (rev _)).      (* found rev_length *)
rewrite (rev_length b).
simpl.
rewrite <- (rev_length t).
rewrite <- Heq_anonymous.
simpl.
auto.
Qed.

Прошел эти этапы и узнал что-то новое. Довольно информативно! Большое спасибо!

Tyl 14.08.2024 20:20

Для справки, мое другое решение последовало предложению ХулиоДиЭгидио в комментарии к вопросу:

Fixpoint half_len {X : Type} (l : list X) : nat :=
  match l with
  | [] => 0
  | _ :: t => match t with
              | [] => 0
              | _ :: t' => S (half_len t')
              end
  end.

Fixpoint firstn {X : Type} (l : list X) (n : nat) : list X
  := match l, n with
     | [], _ => nil
     | _, 0 => nil
     | h :: t, S n' => h :: (firstn t n')
     end.

Definition half {X : Type} (l : list X) : list X :=
  firstn l (half_len l).

На самом деле использовал двух помощников, возможно, это не очень хороший алгоритм.
Приветствуем любые улучшения, спасибо!

Другие вопросы по теме