198 lines
5.9 KiB
Racket
198 lines
5.9 KiB
Racket
#lang scribble/manual
|
|
|
|
@(require (for-label racket/base
|
|
"../main.rkt"))
|
|
|
|
@title{let-assert}
|
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
|
|
|
@defmodule[let-assert]
|
|
|
|
This module provides @racket[let/assert], a small sequential binding form
|
|
with local assertions. It is useful for defensive programming around FFI
|
|
bindings: checks for null pointers, exit codes, and similar failure values can
|
|
be kept close to the binding that produced them, while the body stays on the
|
|
happy path. When an assertion fails, the whole @racket[let/assert] expression
|
|
returns the associated fallback value.
|
|
|
|
@section{Binding with assertions}
|
|
|
|
@defform[
|
|
(let/assert (binding ...) body ...+)
|
|
#:grammar
|
|
([binding [id expr]
|
|
[id expr predicate-expr fallback-expr]])]{
|
|
|
|
The form expands to an internal @racket[let*] structure. Bindings are
|
|
therefore evaluated from left to right, and later bindings may refer to
|
|
earlier ones.
|
|
|
|
A binding of the form @racket[[id expr]] simply binds @racket[id] to
|
|
@racket[expr].
|
|
|
|
A binding of the form
|
|
@racket[[id expr predicate-expr fallback-expr]] evaluates @racket[expr] once,
|
|
applies @racket[predicate-expr] to the result, and binds @racket[id] to that
|
|
result when the predicate accepts it. If the predicate returns @racket[#f],
|
|
the body is not evaluated and the whole @racket[let/assert] form returns
|
|
@racket[fallback-expr].
|
|
|
|
The fallback expression is evaluated only when the assertion fails. Internally,
|
|
the failing assertion raises a private exception carrying that value; the
|
|
exception is caught by @racket[let/assert] itself.
|
|
}
|
|
|
|
@racketblock[
|
|
(let/assert ([x 10 (a->? 0) 'too-small]
|
|
[y (+ x 2) (a-=? 12) 'wrong-value])
|
|
y)
|
|
]
|
|
|
|
The example returns @racket[12]. The second binding can use @racket[x],
|
|
because @racket[let/assert] has @racket[let*] scoping.
|
|
|
|
@section{FFI-style examples}
|
|
|
|
FFI libraries often report errors by returning a null pointer or a negative
|
|
integer status code. With @racket[let/assert], those checks can be written
|
|
next to the operation that may fail.
|
|
|
|
@racketblock[
|
|
(define (open-ffmpeg-instance)
|
|
(let/assert ([fh (fmpg-init) a-!nullptr? #f])
|
|
fh))
|
|
]
|
|
|
|
Here @racket[fmpg-init] is expected to return either a valid native handle or
|
|
@racket[#f]. If the handle is @racket[#f], the complete
|
|
@racket[let/assert] form returns @racket[#f]. Otherwise the valid handle is
|
|
returned.
|
|
|
|
A slightly larger example can combine a pointer check with an FFmpeg-style
|
|
return-code check:
|
|
|
|
@racketblock[
|
|
(define (decode-next! ds)
|
|
(let/assert ([pkt (av-packet-alloc) a-!nullptr? 'packet-allocation-failed]
|
|
[ret (read-selected-audio-packet! ds pkt)
|
|
(a->=? 0)
|
|
'read-packet-failed]
|
|
[pcm (receive-available-frames! ds)
|
|
bytes?
|
|
'decode-failed])
|
|
pcm))
|
|
]
|
|
|
|
The body contains only the successful path. If packet allocation fails, the
|
|
result is @racket['packet-allocation-failed]. If reading the packet returns a
|
|
negative status code, the result is @racket['read-packet-failed]. If decoding
|
|
does not produce bytes, the result is @racket['decode-failed].
|
|
|
|
@section{Creating assertion factories}
|
|
|
|
@defform[(make-assert name not-name pred)]{
|
|
|
|
Defines two assertion factories in a definition context.
|
|
|
|
The form @racket[(name constant)] expands to a unary predicate that applies
|
|
@racket[pred] to the value being checked and @racket[constant]. The form
|
|
@racket[(not-name constant)] expands to the negated variant.
|
|
|
|
For example:
|
|
|
|
@racketblock[
|
|
(make-assert a-eq? a-!eq? eq?)
|
|
]
|
|
|
|
defines @racket[a-eq?] and @racket[a-!eq?]. Consequently,
|
|
@racket[(a-eq? #f)] produces a predicate that accepts values that are
|
|
@racket[eq?] to @racket[#f], while @racket[(a-!eq? #f)] accepts values that
|
|
are not @racket[eq?] to @racket[#f].
|
|
}
|
|
|
|
@section{Assertion factories}
|
|
|
|
@defform[(a-eq? constant)]{
|
|
Produces a unary predicate that accepts values for which
|
|
@racket[(eq? value constant)] is true.
|
|
}
|
|
|
|
@defform[(a-!eq? constant)]{
|
|
Produces a unary predicate that accepts values for which
|
|
@racket[(eq? value constant)] is false.
|
|
}
|
|
|
|
@defform[(a->? constant)]{
|
|
Produces a unary predicate that accepts values greater than
|
|
@racket[constant].
|
|
}
|
|
|
|
@defform[(a-<=? constant)]{
|
|
Produces a unary predicate that rejects values greater than
|
|
@racket[constant].
|
|
}
|
|
|
|
@defform[(a->=? constant)]{
|
|
Produces a unary predicate that accepts values greater than or equal to
|
|
@racket[constant].
|
|
}
|
|
|
|
@defform[(a-<? constant)]{
|
|
Produces a unary predicate that rejects values greater than or equal to
|
|
@racket[constant].
|
|
}
|
|
|
|
@defform[(a-=? constant)]{
|
|
Produces a unary predicate that accepts values numerically equal to
|
|
@racket[constant].
|
|
}
|
|
|
|
@defform[(a-!=? constant)]{
|
|
Produces a unary predicate that accepts values not numerically equal to
|
|
@racket[constant].
|
|
}
|
|
|
|
@section{Ready-made predicates}
|
|
|
|
@defproc[(a-=0? [x number?]) boolean?]{
|
|
Returns @racket[#t] when @racket[x] is numerically equal to zero.
|
|
}
|
|
|
|
@defproc[(a-!=0? [x number?]) boolean?]{
|
|
Returns @racket[#t] when @racket[x] is not numerically equal to zero.
|
|
}
|
|
|
|
@defproc[(a->0? [x real?]) boolean?]{
|
|
Returns @racket[#t] when @racket[x] is greater than zero.
|
|
}
|
|
|
|
@defproc[(a->=0? [x real?]) boolean?]{
|
|
Returns @racket[#t] when @racket[x] is greater than or equal to zero.
|
|
}
|
|
|
|
@defproc[(a-<0? [x real?]) boolean?]{
|
|
Returns @racket[#t] when @racket[x] is less than zero.
|
|
}
|
|
|
|
@defproc[(a-<=0? [x real?]) boolean?]{
|
|
Returns @racket[#t] when @racket[x] is less than or equal to zero.
|
|
}
|
|
|
|
@defthing[a-true? procedure?]{
|
|
A predicate equivalent to @racket[(a-eq? #t)].
|
|
}
|
|
|
|
@defthing[a-false? procedure?]{
|
|
A predicate equivalent to @racket[(a-eq? #f)].
|
|
}
|
|
|
|
@defthing[a-nullptr? procedure?]{
|
|
A predicate equivalent to @racket[(a-eq? #f)]. The name is intended for FFI
|
|
code where @racket[#f] is used to represent a null pointer.
|
|
}
|
|
|
|
@defthing[a-!nullptr? procedure?]{
|
|
A predicate equivalent to @racket[(a-!eq? #f)]. It accepts values that are
|
|
not @racket[#f], which is often the success case for pointer-returning FFI
|
|
calls.
|
|
} |