From 3a92a6552d9f956c14f05cf546070abd9fe7a819 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Mon, 11 May 2026 01:11:55 +0200 Subject: [PATCH] separated contract form from default form. --- contract.rkt | 52 ++++++++++++++ info.rkt | 1 + scrbl/define-return-contract.rkt | 118 +++++++++++++++++++++++++++++++ scrbl/define-return.scrbl | 6 +- 4 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 contract.rkt create mode 100644 scrbl/define-return-contract.rkt diff --git a/contract.rkt b/contract.rkt new file mode 100644 index 0000000..cbb344e --- /dev/null +++ b/contract.rkt @@ -0,0 +1,52 @@ +#lang racket/base + +(require "main.rkt") + +(provide define/contract/return + (all-from-out racket/contract) + (all-from-out "main.rkt") + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; define/return/contract +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define-syntax def-returner + (syntax-rules () + ((_ pred name) + (define/contract (name x) + (-> any/c pred) + (return-value x)) + ) + ) + ) + +(define-syntax call-returner + (syntax-rules () + ((_ x name) + (name x)))) + +(define-syntax define/contract/return + (syntax-rules () + ((_ (name . formals) (c ... last-pred) b1 ...) + (define/contract (name . formals) + (c ... last-pred) + + (let ((ret (λ (x) + (def-returner last-pred name) + (call-returner x name)))) + (with-handlers ([exn:return? ret]) + b1 + ...))) + ) + ((_ name last-pred value) + (define/contract name + last-pred + (let ((ret (λ (x) + (def-returner last-pred name) + (call-returner x name)))) + (with-handlers ([exn:return? ret]) + value))) + ) + ) + ) diff --git a/info.rkt b/info.rkt index e7dd19b..c4e5acc 100644 --- a/info.rkt +++ b/info.rkt @@ -9,6 +9,7 @@ (define scribblings '( ("scrbl/define-return.scrbl" () (library)) + ("scrbl/define-return-contract.scrbl" () (library)) ) ) diff --git a/scrbl/define-return-contract.rkt b/scrbl/define-return-contract.rkt new file mode 100644 index 0000000..c275468 --- /dev/null +++ b/scrbl/define-return-contract.rkt @@ -0,0 +1,118 @@ +#lang scribble/manual + +@(require (for-label racket/base + racket/contract + "../main.rkt")) + +@title{define/return/contract} +@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] + +@defmodule[define-return/contract] + +The @racketmodname[define-return] library provides definition forms with an +explicit early return. This is useful in small defensive functions, and +especially around FFI bindings, where null pointers, error codes, unsupported +states, or failed preconditions should leave the function immediately. + +The early return is implemented with an internal exception. A @racket[return] +raises that exception, and the definition forms catch it around the function +body. The contracted form additionally checks early-returned values against +the result contract. + +This module provides the contracted version of @racket{define/return}. +The module re-exports @racketmodname[racket/contract], so contracts such as +@racket[->], @racket[->*], @racket[any/c], @racket[or/c], +@racket[and/c], and @racket[listof] are available from the same +@racket[require]. + +Note. This can lead to clashes of symbol @racket[->] with module @racketmodname[ffi/unsafe]. + +@section{Contracted definitions} + +@defform*[ +((define/contract/return (name . formals) + (contract-part ... result-contract) + body ...) + (define/contract/return name + result-contract + value)) +]{ + +Like @racket[define/contract], but the body may use @racket[return]. + +The first form defines a contracted function. Ordinary results are checked by +@racket[define/contract]. Early-returned values are checked separately +against @racket[result-contract]. The implementation does this by defining a +small local contracted returner with the same result contract, so the result +contract is passed through Racket's contract machinery. + +The contract must be written inline as a parenthesized contract form. The +last element is used as the early-return result contract. + +@racketblock[ +(define/contract/return (h x) + (-> number? (or/c symbol? number?)) + (when (< x 0) (return 'x-not-positive)) + (let ((y (* x x))) + (when (> y 100) (return 'maxed-out)) + (when (= y 9) (return "Wrong answer!")) + y)) +] + +Here the result contract is @racket[(or/c symbol? number?)]. The symbol +returns are accepted, ordinary numeric results are accepted, and the string +@racket["Wrong answer!"] is rejected. + +@codeblock|{ +(h -1) ; => 'x-not-positive +(h 2) ; => 4 +(h 11) ; => 'maxed-out +(h 3) ; contract violation: string result +}| + +A zero-argument function works in the same way: + +@racketblock[ +(define/contract/return (z) + (-> symbol?) + (let ((cs (current-seconds))) + (when (= (remainder cs 3) 0) (return "deelbaar door 3")) + (when (= (remainder cs 2) 0) (return 'dividable-by-2)) + 'yes)) +] + +The result contract is @racket[symbol?]. Returning +@racket['dividable-by-2] or @racket['yes] is accepted. Returning the string +@racket["deelbaar door 3"] is rejected. + +Rest arguments can be used when the corresponding contract form is accepted +by @racket[define/contract]: + +@racketblock[ +(define/contract/return (sum . xs) + (->* () #:rest (listof number?) number?) + (when (null? xs) (return 0)) + (apply + xs)) +] + +@codeblock|{ +(sum) ; => 0 +(sum 1 2) ; => 3 +(sum 1 'x) ; contract violation +}| + +The second form defines a contracted value. The value expression may use +@racket[return], and the returned value is checked against +@racket[result-contract]. + +@racketblock[ +(define/contract/return v + number? + (return 'ss)) +] + +This definition raises a contract violation, because @racket['ss] does not +satisfy @racket[number?]. + +} + diff --git a/scrbl/define-return.scrbl b/scrbl/define-return.scrbl index 6ee84f1..7e3ee03 100644 --- a/scrbl/define-return.scrbl +++ b/scrbl/define-return.scrbl @@ -19,10 +19,8 @@ raises that exception, and the definition forms catch it around the function body. The contracted form additionally checks early-returned values against the result contract. -The module re-exports @racketmodname[racket/contract], so contracts such as -@racket[->], @racket[->*], @racket[any/c], @racket[or/c], -@racket[and/c], and @racket[listof] are available from the same -@racket[require]. +See @racketmodname[define-return/contract] for the contracted version of +this module. @section{Return}