While trying to express in the contract system Daniel Prager's check that a possibly-buggy square root function actually returns the square root of its argument (and in particular the nice error messages, I ran into a few things that weren't quite as I expected.
My first attempt, using flat-contract-with-explanation, looks like this: (define/contract (squares-to/within/c x error) (-> (>=/c 0) (>=/c 0) flat-contract?) (flat-contract-with-explanation (λ (rslt) (cond [(not (real? rslt)) (λ (blame) (raise-blame-error blame rslt '(expected: "real?" given: "~e") rslt))] [(<= (abs (- x (* rslt rslt))) error) #t] [else (λ (blame) (raise-blame-error blame rslt '(expected: "a number that squares to ~e ± ~e" given: "~e" "\n " given " squared: ~e") x error rslt (* rslt rslt)))])))) (define/contract (buggy-sqrt x) (->i ([x (>=/c 0)]) [rslt (x) (if (= 0 x) (=/c 0) (and/c (>/c 0) (squares-to/within/c x 0.0000001)))]) 1) (Aside: I noticed that despite this thread, flat-contract-with-explanation still does not accept a #:name argument, or at least not in 6.9.) Given the above, (buggy-sqrt 4) produces this fairly unhelpful error message, including none of the nice reporting: buggy-sqrt: broke its own contract promised: (and/c (and/c real? positive?) anonymous-flat-contract) produced: 1 in: the rslt result of (->i ((x (and/c real? (not/c negative?)))) (rslt (x) (if (= 0 x) (=/c 0) (and/c (>/c 0) (squares-to/within/c x 1e-07))))) contract from: (function buggy-sqrt) blaming: (function buggy-sqrt) (assuming the contract is correct) at: unsaved-editor:26.18 I then tried dropping down to make-flat-contract like this: (define/contract (squares-to/within/c-v2 x error) (-> (>=/c 0) (>=/c 0) flat-contract?) (make-flat-contract #:name `(squares-to/within/c-v2 ,x ,error) #:first-order (λ (rslt) (and (real? rslt) (<= (abs (- x (* rslt rslt))) error))) #:late-neg-projection (λ (blame) (λ (rslt neg-party) (cond [(not (real? rslt)) (raise-blame-error blame rslt #:missing-party neg-party '(expected: "real?" given: "~e") rslt)] [(<= (abs (- x (* rslt rslt))) error) rslt] [else (raise-blame-error blame rslt #:missing-party neg-party '(expected: "a number that squares to ~e ± ~e" given: "~e" "\n " given " squared: ~e") x error rslt (* rslt rslt))]))))) (define/contract (buggy-sqrt-v2 x) (->i ([x (>=/c 0)]) [rslt (x) (if (= 0 x) (=/c 0) (and/c (>/c 0) (squares-to/within/c-v2 x 0.0000001)))]) 1) but (buggy-sqrt-v2 4) gives this error: buggy-sqrt-v2: broke its own contract promised: (and/c (and/c real? positive?) (squares-to/within/c-v2 4 1e-07)) produced: 1 in: the rslt result of (->i ((x (and/c real? (not/c negative?)))) (rslt (x) (if (= 0 x) (=/c 0) (and/c (>/c 0) (squares-to/within/c-v2 x 1e-07))))) contract from: (function buggy-sqrt-v2) blaming: (function buggy-sqrt-v2) (assuming the contract is correct) at: unsaved-editor:67.18 which is improved only in that it includes a name: it still doesn't use the nice error message I programed in, and it doesn't even tell me which case of the and/c failed (though that is fairly obvious in this case). Just for completeness, I also tried a struct, like so: (struct squares-to/within-contract (x error) #:property prop:custom-write contract-custom-write-property-proc #:property prop:flat-contract (build-flat-contract-property #:name (match-lambda [(squares-to/within-contract x error) `(squares-to/within/c-v3 ,x ,error)]) #:first-order (match-lambda [(squares-to/within-contract x error) (λ (rslt) (and (real? rslt) (<= (abs (- x (* rslt rslt))) error)))]) #:late-neg-projection (match-lambda [(squares-to/within-contract x error) (λ (blame) (λ (rslt neg-party) (cond [(not (real? rslt)) (raise-blame-error blame rslt #:missing-party neg-party '(expected: "real?" given: "~e") rslt)] [(<= (abs (- x (* rslt rslt))) error) rslt] [else (raise-blame-error blame rslt #:missing-party neg-party '(expected: "a number that squares to ~e ± ~e" given: "~e" "\n " given " squared: ~e") x error rslt (* rslt rslt))])))]))) (define/contract (squares-to/within/c-v3 x error) (-> (>=/c 0) (>=/c 0) flat-contract?) (squares-to/within-contract x error)) (define/contract (buggy-sqrt-v3 x) (->i ([x (>=/c 0)]) [rslt (x) (if (= 0 x) (=/c 0) (and/c (>/c 0) (squares-to/within/c-v3 x 0.0000001)))]) 1) but (buggy-sqrt-v3 4) was like (buggy-sqrt-v2 4) except for the version number. I would also note that using the contract in e.g. (define/contract buggy (and/c number? (squares-to/within/c-v3 4 0.0000001)) 1) gives the nice error I wanted: ../../Applications/Racket v6.9/collects/racket/private/kw.rkt:1170:47: buggy: broke its own contract promised: a number that squares to 4 ± 1e-07 produced: 1 produced squared: 1 in: an and/c case of (and/c number? (squares-to/within/c-v3 4 1e-07)) contract from: (definition buggy) blaming: (definition buggy) (assuming the contract is correct) at: unsaved-editor:131.17 The error message just seems to be lost with some subset of combinators, evidently including ->i. -Philip ---------- Forwarded message ---------- From: Daniel Prager <daniel.a.pra...@gmail.com> Date: Tue, May 2, 2017 at 5:01 PM Subject: [racket-users] Apropos contracts: simple predicates and switching on and off To: Racket Users <racket-users@googlegroups.com> #lang racket (define pre-conditions-on #t) (define post-conditions-on #t) (define (implies a b) (or (not a) b)) (define-syntax-rule (pre [test? msg] ...) (unless (not pre-conditions-on) (unless test? (error (~a "Pre-condition violation: " msg))) ...)) (define-syntax-rule (post [test? msg] ...) (unless (not post-conditions-on) (unless test? (error (~a "Post-condition violation: " msg))) ...)) (define (real-sqrt x) (pre [(real? x) "real argument expected"] [(>= x 0) "non-negative argument expected"]) (define result (sqrt x)) ; Change the implementation to 1, 0, (- x) to trigger various post-condition errors (post [(implies (= x 0) (= result 0)) "The sqrt of zero should be zero"] [(implies (> x 0) (> result 0)) "Positive numbers have positive square-roots"] [(implies (> x 0) (<= (abs (- x (* result result))) 0.0000001)) "result * result = x (to within error)"]) result) (real-sqrt 0) (real-sqrt 9) (real-sqrt -9) ; Pre-condition error -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout. -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.