Idris, a language with dependent types Extended Abstract

Similar documents
Lightweight Invariants with Full Dependent Types

Constructing Correct Circuits: Verification of Functional Aspects of Hardware Specifications with Dependent Types

Idris: Implementing a Dependently Typed Programming Language

Type checking by theorem proving in IDRIS

Embedded Domain Specific Language Implementation using Dependent Types

Type-driven Development of Communicating Systems in Idris

Idris. Programming with Dependent Types. Edwin Brady University of St Andrews, Scotland,

Ivor, a Proof Engine

Alonzo a Compiler for Agda

FUNCTIONAL PEARLS The countdown problem

Shared Subtypes. Subtyping Recursive Parameterized Algebraic Data Types

Provably Correct Software

Correct-by-Construction Concurrency: using Dependent Types to Verify Implementations of Effectful Resource Usage Protocols

Dependently Typed Functional Programming with Idris

Depending on Types. Stephanie Weirich University of Pennsylvania

ABriefOverviewofAgda A Functional Language with Dependent Types

Advanced Type System Features Tom Schrijvers. Leuven Haskell User Group

Dependent Polymorphism. Makoto Hamana

Simple Unification-based Type Inference for GADTs

Type families and data kinds

Coq with Classes. Matthieu Sozeau. Journées PPS 2011 September 5th 2011 Trouville, France. Project Team πr 2 INRIA Paris

Compiling Exceptions Correctly

Dependently Typed Meta-programming

Functional dependencies versus type families and beyond

Deriving Generic Functions by Example

Derivation of a Pattern-Matching Compiler

Formally-Based Resource Usage Verification using a Dependently-Typed MetaLanguage to Specify and Implement Domain-Specific Languages

the application rule M : x:a: B N : A M N : (x:a: B) N and the reduction rule (x: A: B) N! Bfx := Ng. Their algorithm is not fully satisfactory in the

Haskell does it with class: Functorial unparsing

Embedded Domain Specific Languages in Idris Lecture 3: State, Side Effects and Resources

Work-in-progress: "Verifying" the Glasgow Haskell Compiler Core language

Exercise 1 ( = 22 points)

Towards Reasoning about State Transformer Monads in Agda. Master of Science Thesis in Computer Science: Algorithm, Language and Logic.

Ideas over terms generalization in Coq

Static and User-Extensible Proof Checking. Antonis Stampoulis Zhong Shao Yale University POPL 2012

Generic Constructors and Eliminators from Descriptions

The design of a programming language for provably correct programs: success and failure

Programming with dependent types: passing fad or useful tool?

Exercise 1 ( = 18 points)

Lecture 8: Summary of Haskell course + Type Level Programming

Simon Peyton Jones Microsoft Research August 2012

Coercion Quantification

Dependent Types in the Idris Programming Language

The Arrow Calculus (Functional Pearl)

Where is ML type inference headed?

Beluga: A Framework for Programming and Reasoning with Deductive Systems (System Description)

Analysis of dependent types in Coq through the deletion of the largest node of a binary search tree

Generic Programming With Dependent Types: II

The Technology Behind a Graphical User Interface for an Equational Reasoning Assistant

Accurate Step Counting

Type Processing by Constraint Reasoning

Advances in Programming Languages

The Mezzo case. Jonathan Protzenko. François Pottier FSFMA'13. Why design a new programming language?

One Vote for Type Families in Haskell!

Chapter 13: Reference. Why reference Typing Evaluation Store Typings Safety Notes

Types. Benjamin C. Pierce University of Pennsylvania. Programming Languages Mentoring Workshop, Jan. 2012

Introduction. chapter Functions

Heq: a Coq library for Heterogeneous Equality

which a value is evaluated. When parallelising a program, instances of this class need to be produced for all the program's types. The paper commented

Extracting the Range of cps from Affine Typing

A totally Epic backend for Agda

GADTs. Wouter Swierstra. Advanced functional programming - Lecture 7. Faculty of Science Information and Computing Sciences

The Worker/Wrapper Transformation

Adding Constraint Handling Rules to Curry Extended Abstract

Work-in-progress: Verifying the Glasgow Haskell Compiler Core language

Formally Certified Satisfiability Solving

Functional Programming. Big Picture. Design of Programming Languages

Simon Peyton Jones Microsoft Research August 2013

Deductive Program Verification with Why3, Past and Future

Theorem Proving Principles, Techniques, Applications Recursion

First-Class Type Classes

Harvard School of Engineering and Applied Sciences CS 152: Programming Languages

Haskell: From Basic to Advanced. Part 2 Type Classes, Laziness, IO, Modules

Introduction to ML. Based on materials by Vitaly Shmatikov. General-purpose, non-c-like, non-oo language. Related languages: Haskell, Ocaml, F#,

Programming Up-to-Congruence, Again. WG 2.8 Estes Park

KeyNote: Trust Management for Public-Key. 180 Park Avenue. Florham Park, NJ USA.

The Worker/Wrapper Transformation

Chapter 2 The Language PCF

Type-directed Syntax Transformations for ATS-style Programs with Proofs

Overview. Declarative Languages. General monads. The old IO monad. Sequencing operator example. Sequencing operator

Functional Concepts in C++

EPIGRAM 1: a perspective on functional programming with dependent types

A Functional Graph Library

An Algebraic Interface for GETA Search Engine

Lazy State Evaluation of Process Functional Programs

Polymorphic Types in ACL2

Me and my research. Wouter Swierstra Vector Fabrics, 6/11/09

3. Functional Programming. Oscar Nierstrasz

Verified Characteristic Formulae for CakeML. Armaël Guéneau, Magnus O. Myreen, Ramana Kumar, Michael Norrish April 27, 2017

Running Haskell on the CLR

Running Haskell on the CLR

On Agda JAIST/AIST WS CVS/AIST Yoshiki Kinoshita, Yoriyuki Yamagata. Agenda

Eta-equivalence in Core Dependent Haskell

Declaring Numbers. Bernd Braßel, Frank Huch and Sebastian Fischer. Department of Computer Science, University of Kiel, Germany

COP4020 Programming Languages. Functional Programming Prof. Robert van Engelen

Adam Chlipala University of California, Berkeley ICFP 2006

Algebra of Programming using Dependent Types

The war on error. James Chapman University of Tartu

Embedded Domain Specific Languages in Idris

Type checking in the presence of meta-variables

Transcription:

Idris, a language with dependent types Extended Abstract Edwin Brady School of Computer Science, University of St Andrews, St Andrews, Scotland. Email: eb@cs.st-andrews.ac.uk. Tel: +44-1334-463253, Fax: +44-1334-463278 Abstract. Idris is a functional programming language with full dependent types, built on top of Ivor, a theorem proving library for Haskell. The language provides a platform for practical programming with dependent types. Existing systems provide dependent types either through lightweight extensions, retaining a separation between types and values (e.g. GADTs in Haskell) or via full theorem proving requiring total correctness proofs (e.g. Agda, Epigram or Coq). In contrast, in Idris, we provide full dependent types while acknowledging that realistic programs require input/ouput, concurrency, and interfacing with foreign code, and furthermore that totality is not always necessary. We make two main contributions: firstly, we give an implementation of an I/O monad with dependent types and show how, using dependent types, foreign functions can be incorporated directly into the type system without requiring any language extensions; secondly, we exploit the fact that Idris is built on top of a tactic based theorem prover to show how programming considerations can be separated from proof obligations, simplifying program development and readability. 1 Introduction Idris is a functional programming language with full dependent types. It is similar to Epigram [4] or Agda [5], in that it supports dependent pattern matching. It is built on top of the Ivor [1] theorem proving library, and is a pure functional language with a syntax similar to Haskell with GADTs. The purpose of the language is to provide a platform for practical programming with dependent types. 1.1 Motivation Several tools exist today for programming with dependent types, in various forms. These range from extensions to existing languages, such as Haskell s GADTs [?] and open type functions [?] to languages designed with dependent types from the ground up, such as Cayenne [?] and Epigram [4]. Additionally, theorem provers such as Agda [5] and Coq [?] are based on dependent types.

None of these systems, however, are suitable for writing realistic programs with full dependent types. The type system is either limited in some way, restricting the type level functions one can write and thus the properties which can be shown [?], or the focus is on total correctness, which, while clearly desirable, can make many simpler and less safety-critical programs more difficult to write than necessary. In contrast, we are interested in the practical aspects of dependently typed programming we require input and output, concurrency, and foreign function calls. We also acknowledge that at times, particularly in the early stages of software development, it may be necessary to relax some restrictions on the completeness of proofs. Application development and testing should not stall because of a missing lemma! Using our own implementation rather than an existing tool gives complete freedom to experiment with these abstractions and language features beyond the type system, such as I/O and concurrency. Additionally, although unrelated to the work we present in this paper, the core language is intended as an important step towards a fully dependently typed implementation of Hume [2]. 1.2 Contributions This paper introduces Idris, a language with dependent types, which compiles to executable code via C. The paper makes the following contributions: 1. We give an implementation of an I/O monad using dependent types, in the style of Hancock and Setzer [3], which compiles to C, and allows foreign functions (implemented in C) to be executed without requiring any extensions to the language or type system (Section 3). 2. We exploit the fact that Idris is built on top of a tactic based theorem prover, Ivor [1], to show how programming considerations and proof obligations can be neatly separated (Section 4). Additionally, we demonstrate through a larger example, a verified implementation of binary arithmetic which we have previously implemented [?] in Ivor that a practical, realistic and efficient dependently typed programming language can be built with existing technology (Section 4.2). 2 Examples 3 I/O and Foreign Functions We require input/output operations to be executable from within our language. To achieve this, we use Hancock and Setzer s I/O model [3]. This involves implementing a Haskell-style I/O monad [?] by defining commands, representing externally executed I/O operations, and responses which give the type of the value returned by a given command. Our IO monad is then implemented in terms of these of commands and responses:

data IO : where IOReturn : A IO A IODo : (c : Command) (Response c IO A) IO A Here, IODo takes a command, c and an I/O operation that transforms the response to that command into an IO value, and returns the result of applying the I/O operation to the command; and Return simply returns an action packaged as an IO value. To show how this works in practice, we can define simple commands and responses for reading and writing to standard input and output: data Command : where PutStr : String Command GetStr : Command... The responses to these commands are a String, in the case that we have read a string, or the unit type if we have written a string. Response : Command Response (PutStr s) () Response GetStr String... The usual bind and return operations are simple to implement for this monad. We can now write the higher-level reading and writing operations: getstr : IO String getstr = IODo GetStr (λs : String. Return s) putstr : String IO Unit putstr s = IODo (PutStr s) (λx :Unit. Return x) Execution of an I/O program consists of evaluating it as normal and passing the result to an execute meta-operation. This is defined externally to the type theory and simply executes the I/O action at the head of the term, then evaluates and executes the continuation. Execution of the above simple string I/O language is defined by the following pseudo-haskell code (for convenience, we will use Haskell-style do notation, with the obvious translation into our IODo and Return operations): execute (IODo c r) = run c r execute (Return v) = return v run PutStr k = do str getline execute (k str) run (GetStr s) k = do putstr s execute (k unit)... In our compiler, which translates Idris to C, we implement this operation with a compiler pass which partially evaluates the bind operation and translates the commands into their C equivalents.

3.1 Foreign Function Calls The IO monad as defined above requires us to decide which external operations are allowed, and to implement the appropriate run operation. While this does allow us to write realistic programs which communicate with the outside world, it lacks flexibility there is no way to access arbitrary C libraries for, e.g., concurrency, graphics, or networking without modifying the implementation of run. data FType : where FUnit : FType FInt : FType FStr : FType FPtr : FType i ftype : FType i ftype FInt Int i ftype FStr String i ftype FUnit () data ForeignFun : where FFun : String List FType FType ForeignFun data FArgList : List FType where fnil : FArgList Nil fcons : (fx : i f type x) (fxs : FArgList xs) FArgList (Cons x xs) data Command : where... Foreign : (f : ForeignFun) (args : FArgList (f args f )) Command mkforeign : (f : ForeignFun) mkftype f mkforeign (FFun fn args ret) mkfdef fn args Nil fnil ret 4 Interactive Development In writing dependently typed programs, we often find that expressing invariant properties in types means that a certain amount of theorem proving is necessary in order for a definition to typecheck. The following simple definition, for example, does not typecheck: append : Vect A n Vect A m Vect A (m + n) append nil ys ys append (cons x xs) ys cons x (append xs ys) The problem is in the second case; the left hand side and right hand side have different types: append (cons x xs) ys : Vect A (m + (s n)) cons x (append xs ys) : Vect A (s (m + n))

Although these types are different, clearly they are equivalent. We often find that explicit coercions are needed, which prove equivalances between indices, to show that a definition is valid. Such coercions, however, are inconvenient for two reasons: 1. The required type for the coercion can be hard to find, without some assistance from the typechecker, and to write down such types can be tedious. 2. Inserting the coercion can make the code less readable. 4.1 Proposed definitions In fact in this case, we can solve the problem simply by changing the type of append so that it returns Vect A (n + m), but it will not always be so simple. Instead, we offer an alternative means of definign functions using the? operator. Using this operator we can give a proposed definition for a clause, and a name for the coercion we will define later which proves the equivalence between types: append : Vect A n Vect A m Vect A (m + n) append nil ys ys append (cons x xs) ys? cons x (append xs ys) [app cons] This generates a lemma app cons. Before we can run append, we will need to give a definition for app cons. The typechecker has generated the appropriate type for the lemma, and we can use the underlying theorem proving capabilities of Ivor to complete the definition: app cons : A (Vect A n) (Vect A m) (value : Vect A (s (m + n))) Vect A (n + (s m)) The value argument is the proposed return value; we can now see its type, as generated by the typechecker. The return type is the type of the left hand side. The rôle of this lemma is to rewrite the type. All of the variables on the left hand side are passed as additional arguments to give the lemma access to the whole context. The real definition of append (internally, not required to be seen by the programmer) is then: append : Vect A n Vect A m Vect A (m + n) append nil ys ys append (cons x xs) ys app cons x xs ys (cons x (append xs ys)) For the purposes of testing, we provide a function which simply coerces one index to another, without checking. Obviously, any program which makes use of this cannot be trusted! However, it allows us to try proposed definitions and gain some intuition about how we may prove the required lemmas. suspend disbelief : (m : A) (n : A) m = n

4.2 Case study binary arithmetic Many dependently typed programs make use of natural numbers as indices to verify that size invariants are maintained. We gave a more complex example of this technique in [?], where we showed a binary adder to be correct by indexing binary numbers over their natural number equivalents. However, the requirement to use intermediate lemmas to show that the natural number invariants were indeed equivalent did cause the final program to be less readable than desired. To recap briefly, we define bits as follows, indexed over their natural number representation: data Bit : N where O : Bit 0 I : Bit (s 0) We can then build on this to defined pairs of bits, then numbers and numbers with a carry flag, each of which carry the size of the corresponding N in their type. data BitPair : N where bitpair : Bit c Bit v BitPair (v + (s (s 0)) c) data Number : N N where none : Number 0 0 bit : Bit b Number n val Number (s n) (((s (s 0)) n b) + val) data NumCarry : N N where numcarry : Bit c Number n val NumCarry n (((s (s 0)) n c) + val) Assuming we have a means of adding bits, a full carry ripple adder can be implemented as follows, with the full types given. We add the most significant bits, given the result of adding the rest, as follows: addnumberaux : Bit l Bit r NumCarry n val NumCarry (s n) (((s (s 0)) n ) (l + r) + val) addnumberaux x y (numcarry c num) mspair (addbit x y c) num Then the adder is defined recursively: addnumber : Number n l Number n r Bit carry NumCarry n (carry + l + r) addnumber none none c numcarry c none addnumber (bit b1 num1 ) (bit b2 num2 ) c addnumberaux b1 b2 (addnumber num1 num2 c) Unforunately, and unsurprisingly, neither of these definitions typecheck, however convinced we might be that this is a correct implementation. In [?] we explain the reasons in detail, and show how to construct the required intermediate lemmas. Now in Idris, we can achieve this automatically, using the proposed definition syntax.

addnumberaux : Bit l Bit r NumCarry n val NumCarry (s n) (((s (s 0)) n ) (l + r) + val) addnumberaux x y (numcarry c num)? mspair (addbit x y c) num [num aux] addnumber : Number n l Number n r Bit carry NumCarry n (carry + l + r) addnumber none none c? numcarry c none [none case] addnumber (bit b1 num1 ) (bit b2 num2 ) c? addnumberaux b1 b2 (addnumber num1 num2 c) [bit case] We lose little readability from our original failed definition, and additionally are even in a position to test this function, by using the above suspend disbelief function to fill in the proofs. We would prefer, of course, to prove the lemmas completely. The typechecker has given us the lemmas num aux, none case and bit case, which can be proved interactively using Ivor. We give the type of num aux to illustrate: num aux : Bit c Bit l Bit r NumCarry n val (value : NumCarry (s n) (((s (s 0)) n ) (r + y + c) + val)) NumCarry (s n)((s (s 0)) n (l + r) + (((s (s 0)) n ) c + val)) This type is fairly large (and so we are pleased to have the typechecker construct it for us!) but to prove the equivalence of indices requirees straightforward algebraic manipulation. Such proofs could even be built automatically using a Presburger arithmetic solver. The main advantage of our approach is that it cleanly separates these proof requirements from the program, which aids both construction of the complete, verified program and, perhaps more importantly, readability of the program. The full implementation of the binary adder (including a simple I/O interface) is available online, at http://www.cs.st-andrews.ac.uk/ eb/drafts/ binary.idr. 5 Related Work 6 Conclusion We have described the Idris programming languages and shown how to use it to implement a complex problem with dependent types. The language, while not approach the maturity of Haskell or other dependently typed systems such as Agda, demonstrates that existing technology is sufficient to implement realistic and verified programs. Even a framework for foreign functions can be built neatly out of existing work, namely Hancock and Setzer s I/O trees [3]. It is particularly pleasing that foreign functions can be added without making any changes to the core language.

Any program with a sufficiently complex type will require a certain amount of theorem proving to typecheck. Our binary adder is an example of this. While this kind of program is expressible in weaker types systems such as that given by Haskell with open type families [?] the proof obligations become difficult to manage. It is therefore essential to provide some kind of interactive development tools along with any realistic dependently typed language. We have found the proposed definition syntax to reduce the burden on the programmer greatly; it allows a programmer to give the definition they believe to be correct, and even test it, and means that the typechecker generates the required lemma, rather than the programmer. References 1. Lennart Augustsson. Cayenne - a language with dependent types. In Proc. 1998 International Conf. on Functional Programming (ICFP 98), pages 239 250, 1998. 2. Edwin Brady. Ivor, a proof engine. In Implementation and Application of Functional Languages 2006, volume 4449 of LNCS. Springer, 2007. 3. Edwin Brady, Christoph Herrmann, and Kevin Hammond. Lightweight invariants with full dependent types. In Draft Proceedings of Trends in Functional Programming 2008, 2008. 4. Edwin Brady, James McKinna, and Kevin Hammond. Constructing correct circuits: Functional aspects of hardware specifications with dependent types. In Trends in Functional Programming 2007, 2007. 5. Coq Development Team. The Coq proof assistant reference manual. http://coq.inria.fr/, 2001. 6. K. Hammond and G.J. Michaelson. Hume: a Domain-Specific Language for Real- Time Embedded Systems. In Proc. Conf. Generative Programming and Component Engineering (GPCE 03), Lecture Notes in Computer Science. Springer-Verlag, 2003. 7. Peter Hancock and Anton Setzer. Interactive programs in dependent type theory. In P. Clote and H. Schwichtenberg, editors, Proc. of 14th Ann. Conf. of EACSL, CSL 00, Fischbau, Germany, 21 26 Aug 2000, volume 1862, pages 317 331. Springer-Verlag, Berlin, 2000. 8. Conor McBride and James McKinna. The view from the left. Journal of Functional Programming, 14(1):69 111, 2004. 9. Ulf Norell. Towards a practical programming language based on dependent type theory. PhD thesis, 2007. 10. Simon Peyton Jones, Dimitrios Vytiniotis, Stephanie Weirich, and Geoffrey Washburn. Simple unification-based type inference for GADTs. In Proc. 2006 International Conf. on Functional Programming (ICFP 2006), 2006. 11. Simon L. Peyton Jones and Philip Wadler. Imperative functional programming. In Proc. 20th ACM Symposium on Principles of programming languages, pages 71 84, New York, NY, USA, 1993. ACM. 12. Tom Schrijvers, Simon Peyton Jones, Manuel Chakravarty, and Martin Sulzmann. Type checking with open type functions. In ICFP 2008, 2008. To appear.