#lang racket ;;; Examples of using contracts. ;;; ;;; N.B. You must be in 'advanced student' (or full racket) for `require`. ;;; 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 racket/contract) ; Load the library, if not already in full racket. ; racket'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 (define/contract (ink2 n) (-> number? odd?) (+ n 1)) (ink2 4) ; fine (ink2 7) ; a *different* 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. (my-length (list 70 80 90)) ;; (I'd hope that my-length would be caught returning a ;; non-natnum; however in order to always preserve tail-optimization, ;; the contract system assumes your own function is correct and doesn't keep ;; re-checking the contracts for each recursive call. ;; A bit more detail: ;; the contract system only performs the run-time checks when ;; it crosses a "boundary"; the boundary might be defined as the ;; function, the file, or the module, but in all of these cases ;; the function's body is within its own boundary, so recursive calls ;; aren't checked. ;; There are plenty more things the contracts library ;; provides; search "simple functions on contracts" ;; or "between/c" in the help documentation. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #| ;; tl;dr: Instead of (define (prime? n) (and (> (abs n) 1) (for/and {[i (in-range 2 (sqrt (add1 (abs n))))]} (not (zero? (remainder n i)))))) (define (string-times n str) (for/fold {[so-far ""]} {[i (in-range n)]} (string-append so-far str))) ;; write: |# (require racket/contract) ; Load the library. (define/contract (prime? n) (-> integer? boolean?) (and (> (abs n) 1) (for/and {[i (in-range 2 (sqrt (add1 (abs n))))]} (not (zero? (remainder n i)))))) (define/contract (string-primetimes n str) (-> (and/c integer? positive? prime?) string? string?) (for/fold {[so-far ""]} {[i (in-range n)]} (string-append so-far str))) (prime? 103) (string-primetimes 5 "hi") ;(string-primetimes 6 "hi") ; Error: given: 6, which isn't prime?