(** ** The [monotonicity] tactic *)

(** The purpose of the [monotonicity] tactic is to automatically
  select and apply a theorem of the form [Monotonic ?m ?R] in order to
  make progress when the goal is an applied relation. Compared with
  setoid rewriting, [monotonicity] is less powerful, but more direct
  and simple. This means it is easier to debug, and it can seamlessly
  handle dependent types and heterogenous relations.

  The [monotonicity] tactic applies to a goal of the form
  [R (f x1 x2 ... xn) (g y1 y2 ... yn)]. The basic idea is to first
  locate a relational property declared by the user relating [f] and
  [g], namely an instance of [Related f g R'], then to locate and
  instance of [RElim] which will allow us to use [R' f g] to make
  progress on the current goal. Most of the time, [f] and [g] will be
  identical so that the corresponding relational property can be
  writted [Monotonic f R'].

  For instance, we can register the monotonicity of [plus] as an
  instance of [Monotonic plus (lt ++> lt ++> lt)]. Given an goal of
  the form [plus 5 6 < plus x 5], the monotonicity tactic will use
  that property and, using the [RElim] rule for [++>], construct an
  instance of
  [RElim (lt ++> lt ++> lt) plus plus (lt 5 7 -> lt x 5 -> lt (plus 5 6) (plus x 5))]
  to apply on the goal, leaving us with the subgoals [5 < 7] and
  [x < 5].

  While this basic principle is rather straightforward, there are some
  subtleties that we have to consider in order to build a reliable
  system: the relational property we need to use may be about a prefix
  [f x1 x2 ... xk] ([k] < [n]) of the application that still has some
  arguments applied; we want to be able to exploit [subrel]
  relationships to extend the applicability of the declared relational
  properties; we need to be need to be careful about how we treat
  existential variables. *)

(** *** Truncating applications *)

(** The first complication arises from the fact that [f] and [g] may
  themselves be partial applications, so the goal may in fact be of
  the form
  [R (f a1 a2 ... ak x1 x2 ... xn) (g b1 b2 ... bl y1 y2 ... yn)],
  with [k] ≠ [l], where the relational property to apply will be of
  the form [Related R' (f a1 a2 ... ak) (g b1 b2 ... bl)]. This is
  particularly relevant when [f] and [g] may have implicit typeclass
  arguments, or type arguments that cannot be covered by our dependent
  relators without causing universe inconsistency issues.

  Finding the head terms [f] and [g], then locating a property
  [Related R' f g] is rather straightforward, however in this case we
  must only drop the last [n] arguments from the applications before
  we look up the [Related] instance, with no way of guessing the
  correct value of [n]. We want to avoid having to peel off arguments
  one by one and performing a [Related] search at each step, so we ask
  the user to pitch in and declare instances of [Params f n] and
  [Params g n] to guide our search when partial applications with
  heads [f] and [g] are involved.

  Note that the term in the goal itself may only be partially
  applied. Suppose [f x1 x2 ... xn] is in expecting [p] more
  arguments, and that we find an instance of [Params f q]; then we
  should only drop [q - p] arguments to obtain the prefix
  [f x1 x2 ... x_(n+p-q)].

  The class [QueryParams m1 m2 n] asserts that the head terms of [m1]
  and [m2] have [Params] declarations that instruct us to remove [n]
  arguments from each of them before looking up a corresponding
  relational property. *)

Class QueryParams {A B} (m1: A) (m2: B) (n: nat).

(* Specifications: *)
Lemma query_params_one {A B C} (h: A) p (m1: B) (m2: C) n: Params h (p + n) -> QueryParams m1 m2 n.
Lemma query_params_both {A B C D} (h1: A) (h2: B) p1 p2 (m1: C) (m2: D) n: Params h1 (p1 + n) -> Params h2 (p2 + n) -> QueryParams m1 m2 n.