Why haven't newer dependently typed languages adopted SSReflect's approach?

对着背影说爱祢 提交于 2019-12-02 17:33:52
Daniel Schepler

I can provide some thoughts on the first point (defining predicates as boolean-returning functions). My biggest issue with this approach is: then it is by definition impossible for the function to have bugs, even if it turns out what it's calculating isn't what you intended it to calculate. In many cases, it would also obscure what you actually mean by the predicate if you have to include implementation details of the decision procedure for the predicate in its definition.

In mathematical applications, there will also be issues if you want to define a predicate which is a specialization of something which is not decidable in general, even if it happens to be decidable in your specific case. One example of what I'm talking about here would be defining the group with a given presentation: in Coq, a common way to define this would be the setoid with underlying set being formal expressions in the generators, and the equality given by "word equivalence". In general, this relation is not decidable, though in many specific cases it is. However, if you are restricted to defining groups with presentations where the word problem is decidable, then you lose the ability to define the unifying concept which ties all the different examples together, and prove things generically about finite presentations or about finitely presented groups. On the other hand, defining the word equivalence relation as an abstract Prop or equivalent is straightforward (if possibly a little long).

Personally, I prefer to give the most transparent possible definition of the predicate first, and then provide decision procedures where possible (functions returning values of type {P} + {~P} is my preference here, though the boolean-returning functions would work well too). Coq's typeclass mechanism could provide a convenient way to register such decision procedures; for example:

Class Decision (P : Prop) : Set :=
decide : {P} + {~P}.
Arguments decide P [Decision].

Instance True_dec : Decision True := left _ I.
Instance and_dec (P Q : Prop) `{Decision P} `{Decision Q} :
  Decision (P /\ Q) := ...

(* Recap standard library definition of Forall *)
Inductive Forall {A : Type} (P : A->Prop) : list A -> Prop :=
| Forall_nil : Forall P nil
| Forall_cons : forall h t, P h -> Forall P t -> Forall P (cons h t).
(* Or, if you prefer:
Fixpoint Forall {A : Type} (P : A->Prop) (l : list A) : Prop :=
match l with
| nil => True
| cons h t => P h /\ Forall P t
end. *)

Program Fixpoint Forall_dec {A : Type} (P : A->Prop)
  `{forall x:A, Decision (P x)} (l : list A) :
  Decision (Forall P l) :=
  match l with
  | nil => left _ _
  | cons h t => if decide (P h) then
                  if Forall_dec P t then
                    left _ _
                  else
                    right _ _
                else
                  right _ _
  end.
(* resolve obligations here *)
Existing Instance Forall_dec.

This brings decidability by default, opens up more opportunities for proof by computation, and improves checking performance by avoiding the need for the proof engine to carry around large proof terms.

You don't have to carry around large terms as described in the Edwin Brady's thesis under the name of "forcing optimisation". Agda does have forcing which affects type checking (especially how universes are computed is relevant), but I'm not sure if stuff used only at the type check time really gets erased before the run time. Anyway, Agda has two notions of irrelevance: .(eq : p ≡ q) is the usual irrelevance (meaning eq is irrelevant at the type checking time, so it's definitionally equal to any other term of such type) and ..(x : A) is spine irrelevance (not sure if it's a correct term. I think Agda sources call such thing "non-strict irrelevance") which is literally for erasion of computationally irrelevant, but not completely irrelevant terms. So you can define

data Vec {α} (A : Set α) : ..(n : ℕ) -> Set α where
  [] : Vec A 0
  _∷_ : ∀ ..{n} -> A -> Vec A n -> Vec A (suc n)

and n will be erased before the run time. Or at least it seems to be designed so, it's hard to be sure, because Agda has lots of poorly documented features.

And you can write those zero-cost proofs in Coq, just because it too implements irrelevance for stuff that lives in Prop. But irrelevance is built-in into Coq's theory very deeply, while in Agda it's a separate feature, so it's perfectly understandable, why people find use of irrelevance in Coq more readily than in Agda.

One advantage of SSReflect's approach is that it allows reuse, so that for instance many of the functions defined for seq and proofs about them can still be used with tuple (by operating on the underlying seq), whereas with Idris' approach functions like reverse, append and the like need to be rewritten for Vect.

It's not a real reuse if you have to prove properties anyway and those proofs have the same complexity as functions defined over indexed data. It's also inconvenient to do the job of a unification machinery and pass around explicit proofs and apply lemmas to get length xs ≡ n from suc (length xs) ≡ n (and also sym, trans, subst and all other lemmas that a unification machinery can save you from in many cases). Moreover, you lose some clarity by abusing propositional equality: having xs : List A; length xs ≡ n + m instead of xs : Vec A (n + m) doesn't improve readability of your contexts, especially if they are huge which is often the case. And there is another problem: sometimes it's just harder to define a function using SSReflect's approach: you mentioned reverse for Vect, I challenge you to define this function from scratch (with reverse for List as a "reused" part under the hood) and then compare your solution to the definition in Data.Vec from the Agda standard library. And if you don't have irrelevance enabled for propositions by default (which is the case for Agda), then you'd also need to prove properties about proofs if you want to prove, say, reverse (reverse xs) ≡ xs which is a lot of non-trivial boilerplate.

So SSReflect's approach is not perfect. The other one is too. Is there anything that improves upon both? Yes, Ornaments (see Ornamental Algebras, Algebraic Ornaments and The essence of ornaments). You can easily get Vec from List by applying the corresponding ornamental algebra, but I can't say how much code reuse you'll get from it and whether types will drive you nuts or not. I heard people actually use ornaments somewhere.

So it's not that we have an ideal SSReflect's solution and others refuse to adopt it. There is just a hope for a more suitable way to get actual code reuse.

UPDATE

Anton Trunov in their comment made me realize I'm a bit too Agda-minded and people in Coq have tactics which can simplify proofs a lot, so proving in Coq is generally easier (provided you have weapons like crush from the CPDT book) than defining functions over data. Well, then I guess proof irrelevance by default and heavy tactics machinery is what makes SSReflect's approach effective in Coq.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!