call/cc variant of define/return
This commit is contained in:
@@ -2,35 +2,40 @@
|
|||||||
|
|
||||||
@(require (for-label racket/base
|
@(require (for-label racket/base
|
||||||
racket/contract
|
racket/contract
|
||||||
|
(only-in ffi/unsafe cpointer?)
|
||||||
"../contract.rkt"))
|
"../contract.rkt"))
|
||||||
|
|
||||||
@title{define/return/contract}
|
@title{define/contract/return}
|
||||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||||
|
|
||||||
@defmodule[define-return/contract]
|
@defmodule[define-return/contract]
|
||||||
|
|
||||||
The @racketmodname[define-return] library provides definition forms with an
|
The @racketmodname[define-return/contract] library provides
|
||||||
explicit early return. This is useful in small defensive functions, and
|
@racket[define/contract/return], a contracted definition form with an
|
||||||
especially around FFI bindings, where null pointers, error codes, unsupported
|
explicit early return. It is useful in small defensive functions, and
|
||||||
states, or failed preconditions should leave the function immediately.
|
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]
|
The return mechanism is based on @racket[call/cc]. A function definition
|
||||||
raises that exception, and the definition forms catch it around the function
|
captures the continuation around the function body and binds it to the
|
||||||
body. The contracted form additionally checks early-returned values against
|
return identifier supplied by the programmer. Calling that identifier
|
||||||
the result contract.
|
leaves the body and returns the supplied value as the result of the
|
||||||
|
function. No exception is raised and no exception handler is installed.
|
||||||
|
|
||||||
This module provides the contracted version of @racket{define/return}.
|
The module re-exports @racketmodname[racket/contract], so contracts such
|
||||||
The module re-exports @racketmodname[racket/contract], so contracts such as
|
as @racket[->], @racket[->*], @racket[any/c], @racket[or/c],
|
||||||
@racket[->], @racket[->*], @racket[any/c], @racket[or/c],
|
|
||||||
@racket[and/c], and @racket[listof] are available from the same
|
@racket[and/c], and @racket[listof] are available from the same
|
||||||
@racket[require].
|
@racket[require]. It also re-exports @racketmodname[define-return].
|
||||||
|
|
||||||
Note. This can lead to clashes of symbol @racket[->] with module @racketmodname[ffi/unsafe].
|
Note. This can lead to a clash between @racket[->] from
|
||||||
|
@racketmodname[racket/contract] and @racket[->] from
|
||||||
|
@racketmodname[ffi/unsafe].
|
||||||
|
|
||||||
@section{Contracted definitions}
|
@section{Contracted definitions}
|
||||||
|
|
||||||
@defform*[
|
@defform*[
|
||||||
((define/contract/return (name . formals)
|
((define/contract/return (name . formals) return-id
|
||||||
(contract-part ... result-contract)
|
(contract-part ... result-contract)
|
||||||
body ...)
|
body ...)
|
||||||
(define/contract/return name
|
(define/contract/return name
|
||||||
@@ -38,19 +43,27 @@ Note. This can lead to clashes of symbol @racket[->] with module @racketmodname[
|
|||||||
value))
|
value))
|
||||||
]{
|
]{
|
||||||
|
|
||||||
Like @racket[define/contract], but the body may use @racket[return].
|
Like @racket[define/contract], but the function form gives the body a
|
||||||
|
local early-return identifier.
|
||||||
|
|
||||||
The first form defines a contracted function. Ordinary results are checked by
|
The first form defines a contracted function. The @racket[return-id]
|
||||||
@racket[define/contract]. Early-returned values are checked separately
|
identifier is part of the surface syntax. It is not a predefined binding
|
||||||
against @racket[result-contract]. The implementation does this by defining a
|
exported by this module. This keeps the form hygienic: the programmer
|
||||||
small local contracted returner with the same result contract, so the result
|
chooses the name of the local escape continuation, and the body uses that
|
||||||
contract is passed through Racket's contract machinery.
|
same name explicitly.
|
||||||
|
|
||||||
|
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 contract @racket[(-> any/c result-contract)]. The early return value
|
||||||
|
therefore passes through Racket's contract machinery before it leaves the
|
||||||
|
function body.
|
||||||
|
|
||||||
The contract must be written inline as a parenthesized contract form. The
|
The contract must be written inline as a parenthesized contract form. The
|
||||||
last element is used as the early-return result contract.
|
last element is used as the early-return result contract.
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(define/contract/return (h x)
|
(define/contract/return (h x) return
|
||||||
(-> number? (or/c symbol? number?))
|
(-> number? (or/c symbol? number?))
|
||||||
(when (< x 0) (return 'x-not-positive))
|
(when (< x 0) (return 'x-not-positive))
|
||||||
(let ((y (* x x)))
|
(let ((y (* x x)))
|
||||||
@@ -60,8 +73,8 @@ last element is used as the early-return result contract.
|
|||||||
]
|
]
|
||||||
|
|
||||||
Here the result contract is @racket[(or/c symbol? number?)]. The symbol
|
Here the result contract is @racket[(or/c symbol? number?)]. The symbol
|
||||||
returns are accepted, ordinary numeric results are accepted, and the string
|
returns are accepted, ordinary numeric results are accepted, and the
|
||||||
@racket["Wrong answer!"] is rejected.
|
string @racket["Wrong answer!"] is rejected.
|
||||||
|
|
||||||
@codeblock|{
|
@codeblock|{
|
||||||
(h -1) ; => 'x-not-positive
|
(h -1) ; => 'x-not-positive
|
||||||
@@ -73,23 +86,23 @@ returns are accepted, ordinary numeric results are accepted, and the string
|
|||||||
A zero-argument function works in the same way:
|
A zero-argument function works in the same way:
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(define/contract/return (z)
|
(define/contract/return (z) return
|
||||||
(-> symbol?)
|
(-> symbol?)
|
||||||
(let ((cs (current-seconds)))
|
(let ((cs (current-seconds)))
|
||||||
(when (= (remainder cs 3) 0) (return "deelbaar door 3"))
|
(when (= (remainder cs 3) 0) (return "divisible by 3"))
|
||||||
(when (= (remainder cs 2) 0) (return 'dividable-by-2))
|
(when (= (remainder cs 2) 0) (return 'divisible-by-2))
|
||||||
'yes))
|
'yes))
|
||||||
]
|
]
|
||||||
|
|
||||||
The result contract is @racket[symbol?]. Returning
|
The result contract is @racket[symbol?]. Returning
|
||||||
@racket['dividable-by-2] or @racket['yes] is accepted. Returning the string
|
@racket['divisible-by-2] or @racket['yes] is accepted. Returning the
|
||||||
@racket["deelbaar door 3"] is rejected.
|
string @racket["divisible by 3"] is rejected.
|
||||||
|
|
||||||
Rest arguments can be used when the corresponding contract form is accepted
|
Rest arguments can be used when the corresponding contract form is
|
||||||
by @racket[define/contract]:
|
accepted by @racket[define/contract]:
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(define/contract/return (sum . xs)
|
(define/contract/return (sum . xs) return
|
||||||
(->* () #:rest (listof number?) number?)
|
(->* () #:rest (listof number?) number?)
|
||||||
(when (null? xs) (return 0))
|
(when (null? xs) (return 0))
|
||||||
(apply + xs))
|
(apply + xs))
|
||||||
@@ -101,18 +114,65 @@ by @racket[define/contract]:
|
|||||||
(sum 1 'x) ; contract violation
|
(sum 1 'x) ; contract violation
|
||||||
}|
|
}|
|
||||||
|
|
||||||
The second form defines a contracted value. The value expression may use
|
Since @racket[return-id] is a continuation wrapped in a local contracted
|
||||||
@racket[return], and the returned value is checked against
|
procedure, it is called with one value. The continuation should normally
|
||||||
@racket[result-contract].
|
be used only to escape from the dynamic extent of the function body.
|
||||||
|
|
||||||
|
The second form defines a contracted value. The value is checked against
|
||||||
|
@racket[result-contract]. This form has no @racket[return-id] position,
|
||||||
|
so it does not expose a local early-return identifier. Use the function
|
||||||
|
form when a body needs an explicit early return.
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(define/contract/return v
|
(define/contract/return answer
|
||||||
number?
|
number?
|
||||||
(return 'ss))
|
42)
|
||||||
]
|
]
|
||||||
|
|
||||||
This definition raises a contract violation, because @racket['ss] does not
|
This definition succeeds because @racket[42] satisfies
|
||||||
satisfy @racket[number?].
|
@racket[number?].
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(define/contract/return bad-answer
|
||||||
|
number?
|
||||||
|
'not-a-number)
|
||||||
|
]
|
||||||
|
|
||||||
|
This definition raises a contract violation, because
|
||||||
|
@racket['not-a-number] does not satisfy @racket[number?].
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@section{FFI-style null pointers}
|
||||||
|
|
||||||
|
Many FFI bindings represent a native null pointer as @racket[#f]. The
|
||||||
|
explicit return identifier makes it easy to leave a wrapper as soon as a
|
||||||
|
required pointer is missing, while still checking the final result against
|
||||||
|
the function contract.
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(define/contract/return (make-stream-codec-context stream) return
|
||||||
|
(-> cpointer? (or/c cpointer? symbol?))
|
||||||
|
(define codecpar (AVStream-codecpar stream))
|
||||||
|
(unless codecpar
|
||||||
|
(return 'missing-codec-parameters))
|
||||||
|
|
||||||
|
(define codec
|
||||||
|
(avcodec-find-decoder
|
||||||
|
(AVCodecParameters-codec-id codecpar)))
|
||||||
|
(unless codec
|
||||||
|
(return 'decoder-not-found))
|
||||||
|
|
||||||
|
(define ctx (avcodec-alloc-context3 codec))
|
||||||
|
(unless ctx
|
||||||
|
(return 'alloc-codec-context-failed))
|
||||||
|
|
||||||
|
ctx)
|
||||||
|
]
|
||||||
|
|
||||||
|
The checks are sequential. The decoder lookup is evaluated only when
|
||||||
|
@racket[codecpar] is a valid pointer, and the context allocation is
|
||||||
|
evaluated only when @racket[codec] is a valid pointer. A failed pointer
|
||||||
|
check returns the corresponding symbol immediately. A successful path
|
||||||
|
returns @racket[ctx], and both the ordinary result and early-returned
|
||||||
|
symbols are covered by the result contract.
|
||||||
|
|
||||||
|
|||||||
+48
-47
@@ -1,7 +1,6 @@
|
|||||||
#lang scribble/manual
|
#lang scribble/manual
|
||||||
|
|
||||||
@(require (for-label racket/base
|
@(require (for-label racket/base
|
||||||
racket/contract
|
|
||||||
"../main.rkt"))
|
"../main.rkt"))
|
||||||
|
|
||||||
@title{define/return}
|
@title{define/return}
|
||||||
@@ -9,41 +8,32 @@
|
|||||||
|
|
||||||
@defmodule[define-return]
|
@defmodule[define-return]
|
||||||
|
|
||||||
The @racketmodname[define-return] library provides definition forms with an
|
The @racketmodname[define-return] library provides @racket[define/return],
|
||||||
explicit early return. This is useful in small defensive functions, and
|
a small definition form with an explicit early return. It is useful in
|
||||||
especially around FFI bindings, where null pointers, error codes, unsupported
|
compact defensive functions, and especially around FFI bindings, where a
|
||||||
states, or failed preconditions should leave the function immediately.
|
null pointer, an error code, an unsupported state, or a failed precondition
|
||||||
|
should leave the function immediately.
|
||||||
|
|
||||||
The early return is implemented with an internal exception. A @racket[return]
|
The return mechanism is based on @racket[call/cc]. A @racket[define/return]
|
||||||
raises that exception, and the definition forms catch it around the function
|
form captures the current continuation of the function body and binds it to
|
||||||
body. The contracted form additionally checks early-returned values against
|
the return identifier supplied by the programmer. Calling that identifier
|
||||||
the result contract.
|
leaves the body and returns the supplied value as the result of the function.
|
||||||
|
No exception is raised and no exception handler is installed.
|
||||||
|
|
||||||
See @racketmodname[define-return/contract] for the contracted version of
|
@section{Definitions with an early return}
|
||||||
this module.
|
|
||||||
|
|
||||||
@section{Return}
|
@defform[(define/return def return body ...)]{
|
||||||
|
|
||||||
@defform[(return val)]{
|
Like @racket[define], but @racket[body] may call @racket[return] to leave
|
||||||
|
the definition early.
|
||||||
|
|
||||||
Returns @racket[val] from the nearest dynamically enclosing
|
The @racket[return-id] identifier is part of the surface syntax. It is not a
|
||||||
@racket[define/return] or @racket[define/contract/return] body.
|
predefined binding exported by this module. This keeps the form hygienic: the
|
||||||
|
programmer chooses the name of the local escape continuation, and the body
|
||||||
The form is not a general escape continuation. It raises an internal return
|
uses that same name explicitly.
|
||||||
exception. When used outside a body installed by this library, that exception
|
|
||||||
escapes.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@section{Uncontracted definitions}
|
|
||||||
|
|
||||||
@defform[(define/return def body ...)]{
|
|
||||||
|
|
||||||
Like @racket[define], but @racket[body] may use @racket[return] to leave the
|
|
||||||
definition early.
|
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(define/return (status->symbol code)
|
(define/return (status->symbol code) return
|
||||||
(unless (number? code)
|
(unless (number? code)
|
||||||
(return 'not-a-number))
|
(return 'not-a-number))
|
||||||
(when (= code 0) (return 'ok))
|
(when (= code 0) (return 'ok))
|
||||||
@@ -51,9 +41,7 @@ definition early.
|
|||||||
(when (> code 5) (return 'out-of-range))
|
(when (> code 5) (return 'out-of-range))
|
||||||
(cond
|
(cond
|
||||||
((= code 1) 'normal)
|
((= code 1) 'normal)
|
||||||
((>= code 2) (string->symbol (format "code-~a" (* code code))))
|
((>= code 2) (string->symbol (format "code-~a" (* code code))))))
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
The final expression is used when no early return is taken.
|
The final expression is used when no early return is taken.
|
||||||
@@ -69,24 +57,37 @@ The final expression is used when no early return is taken.
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@section{Contracted definitions}
|
@section{FFI-style checks}
|
||||||
|
|
||||||
See @racketmodname[define-return/contract].
|
The form is deliberately small, but it fits the style of many C libraries.
|
||||||
|
For example, a wrapper can leave immediately when an FFI call returns a null
|
||||||
|
pointer:
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(define/return (make-codec-context codec-id) return
|
||||||
|
(define codec (avcodec-find-decoder codec-id))
|
||||||
|
(unless codec
|
||||||
|
(return 'decoder-not-found))
|
||||||
|
|
||||||
|
(define ctx (avcodec-alloc-context3 codec))
|
||||||
|
(unless ctx
|
||||||
|
(return 'alloc-codec-context-failed))
|
||||||
|
|
||||||
|
ctx)
|
||||||
|
]
|
||||||
|
|
||||||
|
The second call is evaluated only when the first one succeeded. If either
|
||||||
|
call returns @racket[#f], the function returns the corresponding symbol.
|
||||||
|
Otherwise the final expression returns the allocated context.
|
||||||
|
|
||||||
@section{Notes}
|
@section{Notes}
|
||||||
|
|
||||||
The mechanism is intentionally small. It uses @racket[with-handlers] and an
|
The mechanism is intentionally just a local escape. It uses @racket[call/cc]
|
||||||
internal exception type; it does not introduce prompts, continuations, or a
|
to capture the continuation around the function body. The captured
|
||||||
new calling convention.
|
continuation is bound to @racket[return] and can be called as a normal
|
||||||
|
procedure with one value.
|
||||||
|
|
||||||
For @racket[define/contract/return], the normal function contract remains the
|
Since @racket[return] is a continuation, not a syntax form, it can also be
|
||||||
job of @racket[define/contract]. The extra returner only exists for the path
|
passed to local helper procedures when that is useful. It should normally be
|
||||||
where @racket[return] leaves the body through an exception handler. That path
|
used only as an escape from the dynamic extent of the function body.
|
||||||
would otherwise bypass the ordinary result position of the function body.
|
|
||||||
|
|
||||||
The contracted form does not try to parse arbitrary contract syntax. It
|
|
||||||
splits the inline contract form syntactically and reuses its last element as
|
|
||||||
the early-return result contract. This works well for ordinary result
|
|
||||||
contracts such as @racket[number?], @racket[symbol?],
|
|
||||||
@racket[(or/c symbol? number?)], and the result position of @racket[->*]
|
|
||||||
contracts.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user