Correct-by-construction programming in Agda

Tutorial at POPL 2019

Andreas Abel and Jesper Cockx

14 January 2019

Introduction to Agda

What is Agda?

Agda is…

  • A strongly typed functional programming language in the tradition of Haskell
  • An interactive theorem prover in the tradition of Martin-Löf

Installation

For this tutorial, you will need to install Agda, the Agda standard library, and the BNFC tool.

Installation instructions:

Main features of Agda

  • Dependent types
  • Indexed datatypes and dependent pattern matching
  • Termination checking and productivity checking
  • A universe hierachy with universe polymorphism
  • Record types with copattern matching
  • Coinductive datatypes
  • Sized types
  • Implicit arguments
  • Instance arguments (~ Haskell’s typeclasses)
  • Parametrized modules (~ ML functors)
  • A FFI to Haskell

We will use many of these in the course of this tutorial!

Emacs mode for Agda

Programs may contain holes (? or {! !}).

  • C-c C-l: typecheck and highlight the current file
  • C-c C-,: get information about the hole under the cursor
  • C-c C-space: give a solution
  • C-c C-r: refine the hole
    • Introduce a lambda or constructor
    • Apply given function to some new holes
  • C-c C-c: case split on a variable

Correct-by-construction programming

Why use dependent types?

With dependent types, we can statically verify that a program satisfies a given correctness property.

Verification is built into the language itself.

Two approaches to verification with dependent types:

  • Extrinsic approach: first write the program and then prove correctness
  • Intrinsic approach: first define the type of programs that satisfy the correctness property and then write the program that inhabits this type

The intrinsic approach is also called correct-by-construction programming.

Example of extrinsic verification

  module Extrinsic where
    _/_ :     
    k / l = 

    _%_ :     
    k % l = 

    divmod-lemma :  {k l}  l * (k / l) + k % l  k
    divmod-lemma = 

    divmod-minimal :  {k l}  k % l < l
    divmod-minimal = 

Example of intrinsic verification

  module Intrinsic where
    record Quotient (k l : ) : Set where
      field
        q r     : 
        divmod  : l * q + r  k
        minimal : r < l

    quotient : (k l : )  Quotient k l
    quotient = 

    _/_ :     
    k / l = Quotient.q (quotient k l)

    _%_ :     
    k % l = Quotient.r (quotient k l)

Correct-by-construction programming

≠ verifying all properties we want

= verifying as many properties as we can get for free

Goal of this tutorial

Implement a correct-by-construction typechecker + interpreter for a C-like language (WHILE)

Structure of a WHILE program

Simple data types and pattern matching

Some simple Agda datatypes

  data Bool : Set where
    true false : Bool

  data Nat : Set where
    zero : Nat
    suc  : Nat  Nat

  data Ord : Set where
    zero : Ord
    suc  : Ord  Ord
    sup  : (Nat  Ord)  Ord

  data  : Set where
    -- no constructors

Pattern matching in Agda

  not : Bool  Bool
  not true  = false
  not false = true

  max : Nat  Nat  Nat
  max zero    l       = l
  max k       zero    = k
  max (suc k) (suc l) = suc (max k l)

  magic : {A : Set}    A
  magic ()

Type and expression syntax for WHILE

  data Type : Set where
    bool int : Type              -- t ::= bool | int

  data Exp : Set where
    eId   : (x : Id)       Exp  -- x,y,z,...
    eInt  : (i : )        Exp  -- ...-2,-1,0,1,2...
    eBool : (b : Boolean)  Exp  -- true or false
    ePlus : (e e' : Exp)   Exp  -- e+e'
    eGt   : (e e' : Exp)   Exp  -- e>e'
    eAnd  : (e e' : Exp)   Exp  -- e&&e'

Statement syntax for WHILE

  data Stm : Set where
    sAss   : (x : Id) (e : Exp)         Stm
      -- ^ x = e;
    sWhile : (e : Exp) (ss : List Stm)  Stm
      -- ^ while (b) { ... }

Program syntax for WHILE

  record Decl : Set where
    constructor dInit   -- t x = e;
    field
      declType : Type   -- variable type (t)
      declId   : Id     -- variable name (x)
      declExp  : Exp    -- initial value (e)
  open Decl public

  record Program : Set where
    constructor program
    field
      theDecls : List Decl  -- t x = e; ...
      theStms  : List Stm   -- ss
      theMain  : Exp        -- printInt(e);
  open Program public

Untyped interpreter

File UntypedInterpreter.agda defines an untyped interpreter for WHILE:

  data Val : Set where
    intV  :         Val
    boolV : Boolean  Val

  Env : Set
  Env = List (Id × Val)

  eval : Env  Exp  Maybe Val
  eval ρ e = 

  execDecl : Decl  Env  Maybe Env
  execDecl d ρ = 

Untyped interpreter (continued)

All Agda functions must be total

But WHILE programs can loop!

⇒ we must limit the number of times we repeat the loop

  execStm : (fuel : )  Stm  Env  Maybe Env
  execStm fuel stm ρ = 

  evalPrg : (fuel : )  Program  Maybe 
  evalPrg fuel (program ds ss e) = 

Exercise #1

Go to AST.agda and extend the syntax with one or more of the following:

  • Boolean negation: !e
  • Integer subtraction: e₁-e₂
  • Conditionals: if (e) { ss₁ } else { ss₂ }

Ignore the pragmas {-# COMPILE ... #-} for now.

Also go to UntypedInterpreter.agda and add the missing cases!

Haskell FFI

Haskell FFI: basics

Import a Haskell module:

  {-# FOREIGN GHC import HaskellModule.hs #-}

Bind Haskell function to Agda name:

  postulate agdaName : AgdaType
  {-# COMPILE GHC agdaName = haskellCode #-}

Bind Haskell datatype to Agda datatype:

  data D : Set where c₁ c₂ : D
  {-# COMPILE GHC D = data hsData (hsCon₁ | hsCon₂) #-}

Haskell FFI example:

  -- In file `AST.agda`:
  {-# FOREIGN GHC import While.Abs #-}
  data Type : Set where
    bool int : Type

  {-# COMPILE GHC Type = data Type
    ( TBool
    | TInt
    ) #-}

BNFC: the Backus-Naur Form Compiler

BNFC is a Haskell library for generating Haskell code from a grammar:

  • Datatypes for abstract syntax
  • Parser
  • Pretty-printer

See While.cf for the grammar of WHILE.

Exercise #2

Extend the BNFC grammar with the new syntactic constructions you added.

Don’t forget to update the Haskell bindings in AST.agda!

Testing the grammar: make parser will compile the parser and run it on /test/gcd.c.

Dependent types and indexed datatypes

Indexed datatypes

Indexed datatype = family of datatypes indexed over some base type

  data Vec (A : Set) :   Set where
    []  : Vec A zero
    _∷_ : {n : }  A  Vec A n  Vec A (suc n)

(like GADTs in Haskell/Ocaml)

Dependent pattern matching

  _++_ : {A : Set} {m n : }
        Vec A m  Vec A n  Vec A (m + n)
  []       ++ ys = ys              -- m = zero
  (x  xs) ++ ys = x  (xs ++ ys)  -- m = suc m′

  head : {A : Set} {n : }  Vec A (suc n)  A
  -- head []         -- Impossible!
  head (x  xs) = x

Well-typed syntax representation

Our correct-by-construction typechecker produces intrinsically well-typed syntax:

  Cxt = List Type

  data Exp (Γ : Cxt) : Type  Set

A term e : Exp Γ t is a well-typed WHILE expression in context Γ.

Well-typed syntax

  Var : (Γ : Cxt) (t : Type)  Set
  Var Γ t = t  Γ

  data Op : (dom codom : Type)  Set where
    plus  : Op int  int
    gt    : Op int  bool
    and   : Op bool bool

  data Exp Γ where
    eInt  : (i : )             Exp Γ int
    eBool : (b : Boolean)       Exp Γ bool
    eOp   : ∀{t t'} (op : Op t t')
           (e e' : Exp Γ t)    Exp Γ t'
    eVar  : ∀{t} (x : Var Γ t)  Exp Γ t

See WellTypedSyntax.agda.

Evaluating well-typed syntax

We can now define eval for well-typed expressions:

  Val : Type  Set
  Val int    = 
  Val bool   = Boolean

  Env : Cxt  Set
  Env = All Val

  eval :  {Γ} {t}  Env Γ  Exp Γ t  Val t
  eval = 

that always returns a value (bye bye Maybe!)

See definition of eval in Interpreter.agda.

Exercise #3

Extend the well-typed syntax with the new syntactic constructions you added.

BREAK (30 min)

Monads and instance arguments

Instance arguments

Instance arguments are Agda’s builtin mechanism for ad-hoc overloading (~ type classes in Haskell).

Syntax:

  • Using an instance: {{x : A}} → ...
  • Defining new instances: instance ...

When using a function of type {{x : A}} → B, Agda will automatically resolve the argument if there is a unique instance of the right type in scope.

Defining a typeclass with instance arguments

  record Print {} (A : Set ) : Set  where
    field
      print : A  String
  open Print {{...}}  -- print : {{r : Print A}} → A → String

  instance
    PrintBool : Print Bool
    print {{PrintBool}} true  = "true"
    print {{PrintBool}} false = "false"

    PrintString : Print String
    print {{PrintString}} x = x

  testPrint : String
  testPrint = (print true) ++ (print "a string")

Monads

Monad is a typeclass with two fields return and _>>=_.

Example: Error monad (see Library/Error.agda)

Correct-by-construction typechecker

See TypeChecker.agda.

Exercise #4

Extend the typechecker with rules for the new syntactic constructions you added.

Coinduction and sized types

Coinduction in Agda

Coinductive type may contain infinitely deep values (non well-founded trees)

    record Stream (A : Set) : Set where
      coinductive
      field
        head : A
        tail : Stream A
    open Stream

    repeat : {A : Set}  A  Stream A
    repeat x .head = x
    repeat x .tail = repeat x

Dealing with infinite computations

Remember: all Agda functions must be total

⇒ interpreter for WHILE takes fuel argument

Can we do better?

Going carbon-free with the Delay monad

Monads allow us to use effects in a pure language

The Delay monad captures the effect of non-termination

A value of type Delay A is

  • either a value of type A produced now
  • or a computation of type Delay A producing a value later

The Delay monad: definition

  mutual
    record Delay (A : Set) : Set where
      coinductive
      field force : Delay' A

    data Delay' (A : Set) : Set where
      now   : A        Delay' A
      later : Delay A  Delay' A

  open Delay public

  never : {A : Set}  Delay A
  never .force = later never

Sized types

Totality requirement: coinductive definitions should be productive: computing each observation should be terminating.

To ensure this, Agda checks that corecursive calls are guarded by constructors, but this is often quite limiting.

A more flexible and modular approach is to use sized types.

The type Size

Size ≃ abstract version of the natural numbers extended with

For each i : Size, we have a type Size< i of sizes smaller than i.

Note: pattern matching on Size is not allowed!

The sized delay monad

  mutual
    record Delay (i : Size) (A : Set) : Set where
      coinductive
      constructor delay
      field
        force : {j : Size< i}  Delay' j A

    data Delay' (i : Size) (A : Set) : Set where
      return' : A          Delay' i A
      later'  : Delay i A  Delay' i A

i ≃ how many more steps are we allowed to observe.

Delay ∞ A is the type of computations that can take any number of steps.

Interpreting well-typed WHILE programs

WHILE statements can have two effects:

  • Modify the environment ⇒ State monad
  • Go into a loop ⇒ Delay monad

We combine both effects in the Exec monad.

The Exec monad

  record Exec {Γ : Cxt} (i : Size) (A : Set) : Set where
    field
      runExec : (ρ : Env Γ)  Delay i (A × Env Γ)
  open Exec public

  execStm :  {Γ} {i} (s : Stm Γ)  Exec {Γ} i 
  execStm = 

  execPrg :  {i} (prg : Program)  Delay i 
  execPrg prg = 

See Interpreter.agda for full code.

Exercise #5

Extend the interpreter with rules for the new syntactic constructions you added.