Experience Report : Type-Driven Development of Certified Tree Algorithms in Coq

This talk is a report on, as well as a defense of the usefulness of dependent types for developing provably correct programs. We believe that dependent types—and dependently-typed programming in Coq in particular—could allow for faster and safer development. There are already several accounts about the utility of dependent types in practical program development, and in real-world applications. Here we add to these accounts by outlining our experience in developing tree algorithms for succinct data structures and proving them with the help of dependent types. 1 Motivation: Formalization of Red-black Trees Ever since its invention, dependently-typed programming has been mostly of theoretical interest. However, recent work has showed that dependently-typed programming could also be useful in practical programming, especially when the program being developed has strict security requirements or complex invariants [4, 13]. Here, we present another case where dependently-typed programming was used to verify such algorithms—in this case, an algorithm that looks innocuous but in fact has rather byzantine invariants. The Problem Implementing deletion for purely functional red-black trees was long known to be a tricky problem. Kahrs and Germane and Might have given relatively simple algorithms [6, 7], but neither comes with a formal proof, and both proved difficult to refactor for our purposes. Also, we could not easily reuse existing Coq formalizations of red-black trees (such as [2]) because our application (to succinct data structures [1]) required nodes decorated with meta-data. We started with a direct translation of Kahrs’ algorithm to Coq, but soon we found ourselves unconvinced of the correctness of our implementation and thus further progress was stalled. Can Types Help? Type-driven development has been advocated in the functional programming community for a long time, but does it apply as well when we want to fully prove an algorithm? Dependent types in Coq allow one to enforce invariants of datastructures and algorithms within their types. It is tempting to use them to implement an algorithm together with its proof, possibly refining the invariants on the go. We will compare that with the more traditional approach of using only ML-style polymorphic types, and writing external lemmas to state the desired correctness properties (this is the approach we used in our main development [1]). For example, let btree be the type of colored binary trees. We can implement balancing in direct-style along the following lines (see [1, file dynamic_redblack.v] for all details): Definition balanceL col (l r : dtree) dl : dtree := match col with | Red => Bnode Red l dl r | Black => match l with | Bnode Red (Bnode Red a da b) dab c => Bnode Red (Bnode Black a da b) dab (Bnode Black c (subD dl dab) r) | . end end. and prove the properties of balancing afterwards, e.g., the fact that balancing does not change the level-order traversal of nodes: Lemma dflatten_balanceL c l r d : dflatten (balanceL c l r d) = dflatten l ++ dflatten r. We also prove lemmas about two orthogonal sets of invariants: the shape invariants which guarantee that our trees are well-formed red-black trees, and the data integrity invariants which state that the meta-data in our data representation correctly captures properties of the represented data. Using dependent types, we can encode the tree invariants inside their types, and implement balancing as a function returning not only the result but also a proof that it preserves the level-order of nodes: Definition balanceL {nl ml d cl cr nr mr} p (l : near_tree nl ml d cl) (r : tree nr mr d cr) : color_ok p (fix_color l) -> color_ok p cr -> {tr : near_tree (nl + nr) (ml + mr) (inc_black d p) p | dflatteni tr = dflatteni l ++ dflatten r}. Here near_tree and tree encode two variants of the red-black invariants, together with the data integrity invariants, so that this type really encodes the full specification of balancing. To define this function, Coq offers two possibilities for complete definitions: tactics or the Program [9] command, both guiding interactive development through the typing context. 2 Red-black Trees Formalized using Dependent Types In this talk, we report on the implementation using dependent types of a library of functions for red-black trees. We have implemented functions for balancing, insertion, and deletion (as well as other functions more specific to succinct data structures) using the three approaches (direct-style, dependent types with tactics, dependent types with Program). We discuss in particular the following points: ∗AIST, Japan; † Nagoya University, Japan; ‡ Tufts University, USA/Nagoya University, Japan 1Also referred to as “compact data structures” in some sources.