Veri¯¬¾cation of Haskell programs using Liquid Haskell Liquid Haskell[VSJ14a], a veri¯¬¾er for Haskell

  • View
    11

  • Download
    0

Embed Size (px)

Text of Veri¯¬¾cation of Haskell programs using Liquid Haskell Liquid Haskell[VSJ14a], a...

  • Verification of Haskell programs using Liquid Haskell

    Morten Aske Kolstad

    Det matematisk-naturvitenskapelige fakultet

    UNIVERSITETET I OSLO

    November 15, 2019

  • Abstract

    Formal verification gives us the obvious benefits of reducing errors and an increased trust in the proven system. However, formal verification of programs are not common-place and are mostly reserved for some special use cases.

    Liquid Haskell[VSJ14a], a verifier for Haskell programs, tries to avoid these issues. With the use of refinement types and an SMT-solver[RR08], it tries to automate a lot of the verification.

    In this thesis Liquid Haskell we investigated Liquid Haskell in two ways. Comparing it to type level programming in Haskell, and also to verify properties of finger trees[HP06].

  • 2

  • Contents

    1 Introduction 5 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2 Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.3 Chapter overview . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.4 Project Source Code . . . . . . . . . . . . . . . . . . . . . . . . 7

    2 Haskell 9 2.1 Kinds and kind polymorphism . . . . . . . . . . . . . . . . . 9 2.2 Datatype promotion using DataKinds . . . . . . . . . . . . . 10 2.3 Generalized Algebraic Data Types - GADTs . . . . . . . . . . 10 2.4 Type families . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

    3 Liquid Haskell 15 3.1 Liquid Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.2 Termination and totality . . . . . . . . . . . . . . . . . . . . . 17

    3.2.1 Termination . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2.2 Totality . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

    4 Comparison of verification in Haskell vs Liquid Haskell 25 4.1 Natural numbers . . . . . . . . . . . . . . . . . . . . . . . . . 25

    4.1.1 Natural numbers in Liquid Haskell . . . . . . . . . . 26 4.1.2 Natural numbers in Dependent Haskell . . . . . . . . 28 4.1.3 Comparison . . . . . . . . . . . . . . . . . . . . . . . . 31

    4.2 Compile time formula validity checking . . . . . . . . . . . . 34 4.2.1 Formula validation in Liquid Haskell . . . . . . . . . 35 4.2.2 Type level formula validity checking in Dependent

    Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 4.2.3 Comparison . . . . . . . . . . . . . . . . . . . . . . . . 40

    5 Case study: Verifying finger trees 43 5.1 Verifying the size of finger trees . . . . . . . . . . . . . . . . . 44

    5.1.1 Size measurement . . . . . . . . . . . . . . . . . . . . 45 5.1.2 Size verification . . . . . . . . . . . . . . . . . . . . . . 47 5.1.3 Generalized finger tree size proof . . . . . . . . . . . . 48 5.1.4 Thoughts . . . . . . . . . . . . . . . . . . . . . . . . . . 50

    5.2 Proving the splitting theorem . . . . . . . . . . . . . . . . . . 50 5.2.1 Proving the splitDigit theorem . . . . . . . . . . . . . 50

    3

  • 5.2.2 Proof of the second conjuct . . . . . . . . . . . . . . . 51 5.2.3 Mutual recursion in finger trees operations . . . . . . 55

    6 Conclusions and Future Work 57 6.1 Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 6.2 Future work . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

    Bibliography 61

    4

  • Chapter 1

    Introduction

    1.1 Motivation

    Issues with formal verification

    Formal verification gives us the obvious benefits of reducing errors, increasing reliability and an increased trust in the proven system. However, formal verification of programs are not common-place and are mostly reserved for some special use cases. The use of theoerem provers or programming languages with the posibility to do formal verification, such as the theorem prover Coq or the dependently typed languages Agda or Idris, are mostly used in academia. There are many reasons why this is the case, but one of the most important reason is that formal verification is hard.

    The techniques used to do formal verification in dependently typed languages use advanced type system and complicated proof tactics. The process of doing formal verifction also takes a lot of effort because of the precision needed, and it often leads to code that is a lot more complex than the code without verification. The performance of code written in dependently typed languages

    Liquid Haskell

    Liquid Haskell, a verifier for Haskell programs, tries to avoid these issues. With the use of refinement types and an SMT-solver, it tries to automate a lot of the verification. Instead of having the user doing proofs using advanced proof tactics, the user can now use equational reasoning. It avoids the performance problem, by being a stand-alone type checker, that does not impact the execution of the program, and therefore the code will be as performant as normal Haskell code.

    It uses the most widely used kind of verification today, the type checker, by extending normal Haskell types with predicates. This is done by using extending the Haskell type system with Liquid types types, which are a

    5

  • restricted form of refinement types designed to increase type inference.

    The goal of this thesis is to find out how usable Liquid Haskell is in practice.

    1.2 Contributions

    The topic of this thesis is to explore Liquid Haskell. To investigate the power of Liquid Haskell, consists of two angles.

    Comparing Haskell and Liquid Haskell is a case study, that compares the two approaches on two problems : proving the commutativity of adding natural numbers, and implementing compile time validity checking of propositional formulas. My contributions from this case study are as follows:

    • A comparison between verification done in Haskell and Liquid Haskell

    • A type level sequent calculus for propositional logic in Haskell

    Verifying finger trees is a second case study. Here we look into the use of Liquid Haskell to verify properties of finger trees. Here we verified properties regarding the size of a finger tree, and also proved a property related to the functionality of splitting a finger tree.

    In particular, my contributions regarding the use of Liquid Haskell on finger trees are the following:

    • An implementation of verified size properties of the multi-purpose data structure finger trees

    • An implementation that uses Liquid Haskell to prove properties of a non non-trivial data structures involving polymorphic recursion and algebraic structures

    1.3 Chapter overview

    Chapter 2 covers language extensions and techniques in Haskell that makes it possible to do type level programming. The topics introduced here, will be used in chapter 4.

    Chapter 3 introduces Liquid Haskell and gives some insight into how it works. It also goes into depth about termination and totality, and why those are important topics in Liquid Haskell.

    Chapter 4 contains a case study that compares doing theorem proving and verification in Haskell to doing it in Liquid Haskell. In particular, it compares the implementations of the solutions to the following problems :

    6

  • • Proving the commutativity of addition of natural numbers

    • Compile time validity checking of propositional formulas

    To do compile time validity checking in Haskell, we introduce a type level sequent calculus for proposition logic.

    Chapter 5 is another case study. This time we are using Liquid Haskell the verify finger trees, a general purpose functional data structure. We first verify important properties regarding the size of a finger tree, and then we prove the a property the relates to how we can split a finger tree based on a predicate.

    Chapter 6 covers related work and introduces ideas for future work. It also summarizes and concludes the thesis.

    1.4 Project Source Code

    All the source code from this thesis can be found at ,along with instructions about how to install the necessary tools and run the programs.

    7

    https://github.com/morteako/liquid-master

  • 8

  • Chapter 2

    Haskell

    The reader of this thesis is expected to have a decent understanding of functional programming and be somewhat familiar with Haskell, or at least be familiar with a somewhat similar language, like SML, OCaml or Idris.

    Therefore basic syntax and functionality, such as type classes, higher order functions, pattern matching, Algebraic Data Types, will not be covered in detail. Knowledge of perhaps the most famous and infamous aspect of Haskell, Monads, is however not needed, because they are not used in this thesis. For a good introduction to Haskell and functional programming, the reader is referred to [Hut16].

    However, in this thesis we will look into advanced techniques and functionality, such as type families, generalized algebraic data types and kinds. These techniques are neceassary when doing the type level programming that we will be doing in Chapter 3. These topics will be explained.

    In this section we first look into the more advanced topics of kinds, kind polymorphism, generalized algebraic data types and type families.

    2.1 Kinds and kind polymorphism

    If we have the following data types

    data T f = MakeT (f Int)

    data Identity a = Identity a

    data MyInt = MyInt Int

    makeT (Identity 1) would typecheck, but makeT (MyInt 1) would not. Why is that?

    Because the "type" of MyInt does not fit the "type" of f in T.

    To understand why it does not fit, we need to understand kinds.