;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-advanced-reader.ss" "lang")((modname contract-examples) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #t #t none #f () #f))) ;#lang racket ;;; Examples of using contracts. ;;; ;;; >>> N.B. You must be in 'intermediate student' (or full racket) to ;;; >>> include contracts. ;;; 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.) ;;; ;;; I. require the library! (require racket/contract) ; Load the library, if not already in full racket. ;;; II. define/contract -- make a function with contracts ; 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))) ;;; IV contracts for structs ; Also a version for define-struct: ; (define-struct/contract book ([title string?] [author string?] [numPages natnum?] [copyR? boolean?]) #:transparent) ; <-- "transparent" = public-fields; ; needed so check-expect can inspect the fields to check equality. ; (The `define-struct` in beginner is transparent by default, ; but not the `define-struct/contract` from full-racket.) ; ; 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?) ;;; IVb contracts involving lists ; 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. ;;; V contracts for union-types -- use the "or/c" of other contracts/predicates: (define lookup-result? (or/c string? false? symbol?)) ; or even better, use 'one-of/c' to create a union-contract from a bunch of individal values: (define lookup-result?-better (or/c string? (one-of/c false 'private))) ;(define list? (or/c empty? cons?)) (define game-result? (or/c boolean? (one-of/c 'tied 'no-game) string?)) ;; 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. ; Is n prime? ; N.B. Uses named-let, so it only works in advanced-student or full-racket. (define/contract (prime? n) (-> any/c boolean?) (and (integer? n) (> (abs n) 1) (let loop {[i 2]} (or (> i (sqrt (add1 (abs n)))) (and (not (zero? (remainder n i))) (loop (add1 i))))))) ; Return `p` copies of `str`, where `p` *must* be prime (else it's a contract-violation). ; N.B. Uses named-let, so it only works in advanced-student or full-racket. (define/contract (string-primetimes p str) (-> (and/c integer? positive? prime?) string? string?) (let my-loop {[so-far ""] [i p]} (cond [(zero? i) so-far] [else (my-loop (string-append so-far str) (sub1 i))]))) (prime? 103) (string-primetimes 5 "hi") ;(string-primetimes 6 "hi") ; Error: given: 6, which isn't prime? ; Here is another version of `prime?` using for/and, instead of named-let #;(define/contract (prime?-v2 n) (-> integer? boolean?) (and (> (abs n) 1) (for/and {[i (in-range 2 (inexact->exact (ceiling (sqrt (add1 (abs n))))) 1)]} (not (zero? (remainder n i)))))) ; Here is another version of `string-primetimes` using for/fold, instead of named-let #;(define/contract (string-primetimes-v2 n str) (-> (and/c integer? positive? prime?) string? string?) (for/fold {[so-far ""]} {[i n]} (string-append so-far str)))