;; The first three lines of this file were inserted by DrScheme. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-intermediate-reader.ss" "lang")((modname contract-examples) (read-case-sensitive #t) (teachpacks ((lib "universe.ss" "teachpack" "2htdp"))) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ((lib "universe.ss" "teachpack" "2htdp"))))) ;;; Examples of using contracts. ;;; ;;; N.B. You must be in 'intermediate student' (or higher). ;;; WARNING: if you are still putting parens in the wrong place, ;;; ignore this and stay in 'beginner' level for a while, ;;; since beginner will catch that mistake for you. ;;; At intermediate level, it's legal to have a function ;;; without calling it, so missing-parens aren't flagged ;;; as errors. ;;; (In particular, we'll be putting functions in the ;;; pre/post conditions, without actually calling them.) ;;; (require scheme/contract) ; Load the library. ; DrScheme's "contracts" aren't compile-time checks; ; they are run-time checks on pre- and post-conditions. (define/contract (foo a b) (-> integer? real? real?) ; compare: foo : int real -> real (+ a b)) (foo 3 4.2) ; works fine. ;(foo 4.2 3) ; Gives an error. ; Note that the signature can have *any* function, ; not just the standard types: ; (define/contract (ink n) (-> even? odd?) ; even? -> odd? (+ n 1)) (ink 4) ; fine ;(ink 7) ; error ; Some helpful numeric predicates: ; number? complex? real? inexact? exact? rational? integer? ; positive? negative? zero? even? odd? ; ; You can also write your own functions, ; and use them as contracts. I use 'natnum?' all the time: ; (define (natnum? val) (and (number? val) (integer? val) (not (negative? val)))) ; ; Note that `natural-number/c` is built-in to the contract lib. ; You could also write: (define nat/c natural-number/c) ; Careful; you can't call `(natural-number/c 17)`. ; While all predicates are contracts, ; not all contracts are predicates. ; (More precisely: if -> is handed a predicate function, ; -> turns it into a contract.) ; What about functions which take in *any* input? ; Use the pre-existing contract 'any/c': ; (define/contract (long-string? val) (-> any/c boolean?) (and (string? val) (> (string-length val) 10))) ; Also a version for define-struct: ; (define-struct/contract book ([title string?] [author string?] [numPages natnum?] [copyR? boolean?])) ; ; Now the constructor, accessors, and predicates ; have contracts, so you can't create a struct with ; the wrong types inside. (define-struct dvd (title time)) ; For union types, we can either make our own function: (define (lib-item? val) (or (book? val) (dvd? val))) ; and then use (say) ; (-> lib-item? natnum?) ; or we could use 'or/c', a function which takes in contracts ; and returns a new contract: ; (-> (or/c book? dvd?) natnum?) ; For lists, use 'listof': (define/contract (my-length nums) (-> (listof number?) natnum?) (cond [(empty? nums) 0] [(cons? nums) (add1 (my-length (rest nums)))])) ;; Bug: If you replace the above `0` with (say) a string, ;; `add1` will raise the error that it was given a string. ;; (I'd hope that my-length would be caught returning a ;; non-natnum; but as an optimization, the contract system ;; assumes your own function is correct and doesn't keep ;; re-checking the contracts for each recursive call.) ;; There are plenty more things the contracts library ;; provides; search "simple functions on contracts" ;; or "between/c" in the help documentation.