(** *** Dependent products *)

(** Now we consider the dependent case. The definition of [forall_rel]
  is somewhat involved, but you can think of relating [f] and [g] in
  the context of a structure-preserving transformation from a quiver
  ([V], [E]) to the quiver ([Type], [rel]). Like a functor, it has two
  components: [FV] maps nodes to types, and [FE] maps edges to
  relations. Then, [forall_rel FE f g] states that given an edge
  [(e : E v1 v2)], the images [f v1] and [g v2] are related by the
  corresponding relation [FE v1 v2 e]. We will write [forall_rel FE f g]
  as [(forallr e @ v1 v2 : E, FE[v1,v2,e]) f g]. Notice that this notation
  binds [v1] and [v2] as well as [e].

  If that makes no sense, you can think of specific source quivers. So
  for instance, oftentimes we will want to use ([Type], [rel]) as the
  source quiver too. This corresponds to parametric polymorphism. The
  type of [Some] is [forall A : Type, A -> option A]; the corresponding
  logical relation is [forallr R @ A1 A2 : rel, R ++> option_rel R]. Stating
  that [Monotonic Some (foralr R @ A1 A2 : rel, R ++> option_rel R)] means that,
  given any relation [R] and elements [x1] and [x2], if [R] relates
  [x1] and [x2], then [option_rel R] will relate [Some x1] and [Some x2].

  Another example from [liblayers] is the quiver of our data-indexed
  simulation relations [simrel : layerdata -> layerdata -> Type].
  Here the structure-preserving transformations are our simulation
  relation diagrams, which have types such as
  [lsim : forall D1 D2, simrel D1 D2 -> rel (layer D1) (layer D2)] or
  [psim : forall {D1 D2}, simrel D1 D2 -> rel (primsem D1) (primsem D2)].
  Then, the monotonicity of a data-indexed function —
  say, [foo: forall D : layerdata, layer D -> primsem D] —
  can be expressed as
  [Monotonic foo (forall R @ D1 D2 : simrel, siml D1 D2 R ++> simp D1 D2 R) foo].

  This definition is the same as [respectful_hetero]. *)

Definition forall_rel {V1 V2} {E: V1->V2->Type} {FV1: V1->Type} {FV2: V2->Type}:
    (forall v1 v2, E v1 v2 -> rel (FV1 v1) (FV2 v2)) ->
    rel (forall v1, FV1 v1) (forall v2, FV2 v2) :=
  fun FE f g =>
    forall v1 v2 (e: E v1 v2), FE v1 v2 e (f v1) (g v2).Notation "'forallr' e @ v1 v2 : E , R" :=
  (forall_rel (E := E) (fun v1 v2 e => R))
  (at level 200, e ident, v1 ident, v2 ident, right associativity) : rel_scope.Notation "'forallr' e @ v1 v2 , R" :=
  (forall_rel (fun v1 v2 e => R))
  (at level 200, e ident, v1 ident, v2 ident, right associativity) : rel_scope.Notation "'forallr' e : E , R" :=
  (forall_rel (E := E) (fun _ _ e => R))
  (at level 200, e ident, right associativity) : rel_scope.Notation "'forallr' e , R" :=
  (forall_rel (fun _ _ e => R))
  (at level 200, e ident, right associativity) : rel_scope.

(* Specifications: *)
Lemma forall_rintro {V1 V2 E F1 F2} (FE: forall x y, _ -> rel _ _) f g: RIntro (forall u v e, FE u v e (f u) (g v)) (@forall_rel V1 V2 E F1 F2 FE) f g.
Lemma forall_relim {V1 V2 E FV1 FV2} R f g v1 v2 e P Q: RElim (R v1 v2 e) (f v1) (g v2) P Q -> RElim (@forall_rel V1 V2 E FV1 FV2 R) f g P Q.