Introduction to Contracts and Functional Contracts

download Introduction to Contracts and Functional Contracts

If you can't read please download the document

description

Prepared for a talk to the Melbourne Functional User Group, September 6, 2013.

Transcript of Introduction to Contracts and Functional Contracts

  • 1. Daniel Prager @agilejitsu agilejitsu.blogspot.com Melbourne Functional User Group, September 6, 2013 There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult. It demands the same skill, devotion, insight, and even inspiration as the discovery of the simple physical laws which underlie the complex phenomena of nature. Tony Hoare, in his 1980 Turing Award lecture Introduction to Contracts and Functional Contracts

2. A personal Preamble 1998: I discover Design by Contract and start using it Time spent debugging: 90% Joy I've used variants ever since Technical pay-off: Hit rate in persuading colleagues to join in: variable In this talk I Introduce the basics of Design by Contract Show how it can work in combination with functional programming Share some hard-won advice Ideally chat with you about where DbC sits today in relation to automated testing and static type-checking 3. Overview The Problem of Software Quality Contracts: The basic idea Design By Contract (DbC) Questions and Concerns Functional Contracts Comparison with Tests and with Static Type-checking Some Hybrid Approaches Conclusion 4. The Problem of Software Quality Problem: How to design and program correct, robust, maintainable, etc., etc. software? Assumption: BUGS happen! Prevention: Simplicity and clarity of design; modularity; concision Treatment: Rapid isolation and diagnosis of problems 5. Contracts: a metaphor Commercial Contract The client pays a supplier to provide a good or service The contract makes explicit the expectations on both parties specifies who is to blame client or supplier if something goes wrong. Programming Contract The calling code (the client) calls a function (the supplier) The contract defines explicit pre-conditions on arguments passed by the calling code (and on the initial state) defines explicit post-conditions on the result of the function (and the final state) specifies which code is to blame calling code or function if something goes wrong. 6. The standard example in Eiffel square_root (x: REAL): REAL is -- Returns the positive square root of x require -- Pre-condition section positive_argument: x >= 0.0 do -- Implementation section ... ensure -- Post-condition section correct_root: abs(Result * Result x) = 0 end 7. Same example in Racket with hand-rolled contract (define (square-root x) (require (non-negative-real? x) "real, non-negative argument") (let ([result ...]) (ensure (non-negative-real? result) "real, non-negative result") (ensure (approx-equal? (* result result) x) "correct root") result)) (define (require test [message ]) (unless test (error 'pre-condition-violation message))) (define (ensure test [message ]) (unless test (error 'post-condition-violation message))) (define (non-negative-real? x) (and (real? x) (>= x 0))) (define (approx-equal? x y [tol 0.000001]) (i ((x non-negative-real?)) (r non-negative-real?) #:post (r x) ...) contract from: .../contracts.rkt Blaming: .../contracts.rkt At: .../contracts.rkt: [line/col of the contract] 26. Higher-order functions and Contracts Higher-order functions can't be checked immediately for conformance to a predicate. E.g. (define/contract (make-indenter n) (->i ([n natural-number/c]) [r (n) (->i ([s string?]) [result string?] #:post (s result) (= (string-length result) (+ n (string-length s))))]) ( (s) (string-append (make-string n #space) s))) Usage: ((make-indenter 4) foo) foo 27. Higher order functions and Contracts Racket wraps the higher-order functions in a guard and checks what's passed in and returned at the time of function application. Failures are deciphered in the error message. E.g. ((make-indenter 4) 'foo) make-indenter: contract violation expected: string? given: 'foo in: the s argument of the r result of (->i ((n natural-number/c)) (r (n) ...)) 28. Attaching contracts at module boundaries Traditionally, contracts are enforced at function boundaries, but other choices are possible. In Racket, contracts are commonly wrapped around existing functions (and data) when they are exported from modules. Contract checking only occurs across module boundaries, useful e.g. in highly recursive scenarios. 29. Example of adding contracts at the module level (module parity racket (provide (contract-out [odd? (-> integer? boolean?)] [even? (-> integer? boolean?)])) (define (odd? n) (cond [(zero? n) #f] [(negative? n) (odd? (- n))] [else (even? (sub1 n))])) (define (even? n) (if (zero? n) #t (odd? (sub1 n))))) 30. Additional facilities in Racket Racket includes many more features for working with contracts, including: Support for variable-arity and keyword arguments Contracts on structures Additional contract combinators See the Racket docs for details 31. Interlude: What do you do now? Questions for fans of automated tests: TDD? BDD? CI? What do you like about automated tests? Pains? Questions for fans of static typing: How sophisticated is your type-system? Is anyone using dependent types? Is anyone automatically generating tests from types? Likes? Pains? Any other approaches? 32. Automated Tests smackdown Positives of tests Popular Concrete and relatively easy to understand Automated tests exercise the code No performance impact on production code TDD encourages good design Negatives of tests: Requires discipline Tests don't apportion precise blame (although TDD / rolling back can help isolate issues) Not helpful in production A big test suite can be time- consuming to run and take a lot of work to maintain 33. Static Type-Checking smackdown Positives Type checks are at compile time: early feedback Faster execution Simplifies contracts Enforces discipline Limitation You can't check everything statically: Rice's theorem Negatives Demands discipline Can break programmer flow Can result in lots of boilerplate (Java, C#, etc.) Large systems can be slow to compile Simpler type systems can be unduly restrictive and inflexible Languages with superior type systems are fairly challenging to learn (Haskell is hard) Language / tool support is essential 34. Contracts smackdown Positives Assigns blame accurately Crushes debugging time Simplifies code and tests Can express elaborate checks Simplifies documentation Helps clarify design and improve modularity Most of the benefit can be achieved without out-of-the- box language support Negatives Demands discipline Demands skill Not widely known or used Can slow down performance, e.g. by inflating algorithmic complexity if you're not careful 35. Hybrid Approach #1 Contracts + Static Type-Checking Maximum concision This was the original mix designed into Eiffel Challenges? High-ish discipline factor No out-of-the-box contracts yet in Typed Racket For functional programming, rolling your own higher-order contract support is non-trivial. 36. Hybrid Approach #2 Tests + Contracts: Option 1: use explicit pre-conditions don't use explicit post-conditions; use tests instead Option 2 (my favourite): use scenario tests to exercise code and drive Continuous Integration Use pre-conditions and post-conditions instead of unit tests Option 3: automatically generate random unit tests from contracts 37. Hybrid Approach #3 Tests + Contracts + Types: Worthwhile once you're comfortable with all three, especially for large and complex software Challenges High discipline approach Difficult to get everyone in a team on board 38. Conclusion If you consistently write automated tests, you have the necessary self-discipline to try contracts. If you are a fan of static type-checking, you can approach contracts as a logical, dynamic extension. Using contracts consistently should greatly reduce the time you spend debugging make your code, tests, and documentation more concise and readable clarify and simplify your design choices change the way you think! Acknowledgements: Thanks to Matthias Felleisen, Greg Hendershott, Robby Findler and Russell Sim, respectively for helpful critique and suggestions, encouragement, a just-in-time correction, and for suggesting I give a talk.