call/cc variant of define/return
This commit is contained in:
+48
-47
@@ -1,7 +1,6 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require (for-label racket/base
|
||||
racket/contract
|
||||
"../main.rkt"))
|
||||
|
||||
@title{define/return}
|
||||
@@ -9,41 +8,32 @@
|
||||
|
||||
@defmodule[define-return]
|
||||
|
||||
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 @racketmodname[define-return] library provides @racket[define/return],
|
||||
a small definition form with an explicit early return. It is useful in
|
||||
compact defensive functions, and especially around FFI bindings, where a
|
||||
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]
|
||||
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 return mechanism is based on @racket[call/cc]. A @racket[define/return]
|
||||
form captures the current continuation of the function body and binds it to
|
||||
the return identifier supplied by the programmer. Calling that identifier
|
||||
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
|
||||
this module.
|
||||
@section{Definitions with an early return}
|
||||
|
||||
@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
|
||||
@racket[define/return] or @racket[define/contract/return] body.
|
||||
|
||||
The form is not a general escape continuation. It raises an internal return
|
||||
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.
|
||||
The @racket[return-id] identifier is part of the surface syntax. It is not a
|
||||
predefined binding exported by this module. This keeps the form hygienic: the
|
||||
programmer chooses the name of the local escape continuation, and the body
|
||||
uses that same name explicitly.
|
||||
|
||||
@racketblock[
|
||||
(define/return (status->symbol code)
|
||||
(define/return (status->symbol code) return
|
||||
(unless (number? code)
|
||||
(return 'not-a-number))
|
||||
(when (= code 0) (return 'ok))
|
||||
@@ -51,9 +41,7 @@ definition early.
|
||||
(when (> code 5) (return 'out-of-range))
|
||||
(cond
|
||||
((= 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.
|
||||
@@ -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}
|
||||
|
||||
The mechanism is intentionally small. It uses @racket[with-handlers] and an
|
||||
internal exception type; it does not introduce prompts, continuations, or a
|
||||
new calling convention.
|
||||
The mechanism is intentionally just a local escape. It uses @racket[call/cc]
|
||||
to capture the continuation around the function body. The captured
|
||||
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
|
||||
job of @racket[define/contract]. The extra returner only exists for the path
|
||||
where @racket[return] leaves the body through an exception handler. That path
|
||||
would otherwise bypass the ordinary result position of the function body.
|
||||
Since @racket[return] is a continuation, not a syntax form, it can also be
|
||||
passed to local helper procedures when that is useful. It should normally be
|
||||
used only as an escape from the dynamic extent 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