
Port `examples`, `interactions`, etc., to use the new `examples` form of `scribble/examples`. The main intended effect is to ensure that errors are produced by examples only as specifically indicated.
295 lines
12 KiB
Racket
295 lines
12 KiB
Racket
#lang scribble/manual
|
|
@(require (for-label racket/base racket/generic))
|
|
|
|
@title[#:tag "struct-generics"]{Generic Interfaces}
|
|
|
|
@defmodule[racket/generic]
|
|
|
|
|
|
A @deftech{generic interface} allows per-type methods to be
|
|
associated with generic functions. Generic functions are defined
|
|
using a @racket[define-generics] form. Method implementations for
|
|
a structure type are defined using the @racket[#:methods] keyword
|
|
(see @secref["define-struct"]).
|
|
|
|
@defform/subs[(define-generics id
|
|
generics-opt ...
|
|
[method-id . kw-formals*] ...
|
|
generics-opt ...)
|
|
([generics-opt
|
|
(code:line #:defaults ([default-pred? default-impl ...] ...))
|
|
(code:line #:fast-defaults ([fast-pred? fast-impl ...] ...))
|
|
(code:line #:fallbacks [fallback-impl ...])
|
|
(code:line #:defined-predicate defined-pred-id)
|
|
(code:line #:defined-table defined-table-id)
|
|
(code:line #:derive-property prop-expr prop-value-expr)]
|
|
[kw-formals* (arg* ...)
|
|
(arg* ...+ . rest-id)
|
|
rest-id]
|
|
[arg* arg-id
|
|
[arg-id]
|
|
(code:line keyword arg-id)
|
|
(code:line keyword [arg-id])])]{
|
|
|
|
Defines the following names, plus any specified by keyword options.
|
|
|
|
@itemlist[
|
|
|
|
@item{@racketidfont{gen:}@racket[id] as a transformer binding for
|
|
the static information about a new generic interface;}
|
|
|
|
@item{@racket[id]@racketidfont{?} as a predicate identifying
|
|
instances of structure types that implement this generic group; and}
|
|
|
|
@item{each @racket[method-id] as a @deftech{generic method} that calls the
|
|
corresponding method on values where
|
|
@racket[id]@racketidfont{?} is true.
|
|
Each @racket[method-id]'s @racket[kw-formals*] must include a required
|
|
by-position argument that is @racket[free-identifier=?] to
|
|
@racket[id]. That argument is used in the generic definition to
|
|
locate the specialization.}
|
|
|
|
@item{@racket[id]@racketidfont{/c} as a contract combinator that
|
|
recognizes instances of structure types which implement the
|
|
@racketidfont{gen:}@racket[id] generic interface. The combinator
|
|
takes pairs of @racket[method-id]s and contracts. The contracts
|
|
will be applied to each of the corresponding method implementations.
|
|
The @racket[id]@racketidfont{/c} combinator is intended to be used to
|
|
contract the range of a constructor procedure for a struct type that
|
|
implements the generic interface.}
|
|
|
|
]
|
|
|
|
The @racket[#:defaults] option may be provided at most once.
|
|
When it is provided, each generic function
|
|
uses @racket[default-pred?]s to dispatch to the given
|
|
@deftech{default method} implementations,
|
|
@racket[default-impl]s, if dispatching to the generic method table fails.
|
|
The syntax of the @racket[default-impl]s is the same as the methods
|
|
provided for the @racket[#:methods] keyword for @racket[struct].
|
|
|
|
The @racket[#:fast-defaults] option may be provided at most once.
|
|
It works the same as @racket[#:defaults], except the @racket[fast-pred?]s are
|
|
checked before dispatching to the generic method table. This option is
|
|
intended to provide a fast path for dispatching to built-in datatypes, such as
|
|
lists and vectors, that do not overlap with structures implementing
|
|
@racketidfont{gen:}@racket[id].
|
|
|
|
The @racket[#:fallbacks] option may be provided at most once.
|
|
When it is provided, the @racket[fallback-impl]s define
|
|
@deftech{fallback method} implementations
|
|
that are used for any instance of the generic interface that does not supply a
|
|
specific implementation. The syntax of the @racket[fallback-impl]s is the same
|
|
as the methods provided for the @racket[#:methods] keyword for @racket[struct].
|
|
|
|
The @racket[#:defined-predicate] option may be provided at most once.
|
|
When it is provided, @racket[defined-pred-id] is defined as a
|
|
procedure that reports whether a specific instance of the generic interface
|
|
implements a given set of methods.
|
|
Specifically, @racket[(defined-pred-id v 'name ...)] produces @racket[#t] if
|
|
@racket[v] has implementations for each method @racket[name], not counting
|
|
@racket[#:fallbacks] implementations, and produces @racket[#f] otherwise.
|
|
This procedure is intended for use by
|
|
higher-level APIs to adapt their behavior depending on method
|
|
availability.
|
|
|
|
The @racket[#:defined-table] option may be provided at most once.
|
|
When it is provided, @racket[defined-table-id] is defined as a
|
|
procedure that takes an instance of the generic interface and returns an
|
|
immutable @tech{hash table} that maps symbols corresponding to method
|
|
names to booleans representing whether or not that method is
|
|
implemented by the instance. This option is deprecated; use
|
|
@racket[#:defined-predicate] instead.
|
|
|
|
The @racket[#:derive-property] option may be provided any number of times.
|
|
Each time it is provided, it specifies a @tech{structure type property} via
|
|
@racket[prop-expr] and a value for the property via @racket[prop-value-expr].
|
|
All structures implementing the generic interface via @racket[#:methods]
|
|
automatically implement this structure type property using the provided values.
|
|
When @racket[prop-value-expr] is executed, each @racket[method-id] is bound to
|
|
its specific implementation for the @tech{structure type}.
|
|
|
|
If a value @racket[v] satisfies @racket[id]@racketidfont{?}, then @racket[v] is
|
|
a @deftech{generic instance} of @racketidfont{gen:}@racket[id].
|
|
|
|
If a generic instance @racket[v] has a corresponding implementation for some
|
|
@racket[method-id] provided via @racket[#:methods] in @racket[struct] or via
|
|
@racket[#:defaults] or @racket[#:fast-defaults] in @racket[define-generics],
|
|
then @racket[method-id] is an @deftech{implemented generic method} of
|
|
@racket[v].
|
|
|
|
If @racket[method-id] is not an implemented generic method of a generic
|
|
instance @racket[v], and @racket[method-id] has a fallback implementation that
|
|
does not raise an @racket[exn:fail:support] exception when given @racket[v],
|
|
then @racket[method-id] is a @deftech{supported generic method} of @racket[v].
|
|
|
|
}
|
|
|
|
@defproc[(raise-support-error [name symbol?] [v any/c]) none/c]{
|
|
|
|
Raises an @racket[exn:fail:support] exception for a @techlink{generic
|
|
method} called @racket[name] that does not support the @techlink{generic
|
|
instance} @racket[v].
|
|
|
|
@examples[#:eval evaluator
|
|
(eval:error (raise-support-error 'some-method-name '("arbitrary" "instance" "value")))
|
|
]
|
|
|
|
}
|
|
|
|
@defstruct*[(exn:fail:support exn:fail) () #:transparent]{
|
|
|
|
Raised for @techlink{generic methods} that do not support the given
|
|
@techlink{generic instance}.
|
|
|
|
}
|
|
|
|
@defform[(define/generic local-id method-id)
|
|
#:contracts
|
|
([local-id identifier?]
|
|
[method-id identifier?])]{
|
|
|
|
When used inside the method definitions associated with the
|
|
@racket[#:methods] keyword, binds @racket[local-id] to the generic for
|
|
@racket[method-id]. This form is useful for method specializations to
|
|
use generic methods (as opposed to the local specialization) on other
|
|
values.
|
|
|
|
Using the @racket[define/generic] form outside a @racket[#:methods]
|
|
specification in @racket[struct] (or @racket[define-struct]) is an
|
|
syntax error.}
|
|
|
|
|
|
@; Examples
|
|
@(require scribble/eval)
|
|
@(define (new-evaluator)
|
|
(let* ([e (make-base-eval)])
|
|
(e '(require (for-syntax racket/base)
|
|
racket/contract
|
|
racket/generic))
|
|
e))
|
|
|
|
@(define evaluator (new-evaluator))
|
|
|
|
@examples[#:eval evaluator
|
|
(define-generics printable
|
|
(gen-print printable [port])
|
|
(gen-port-print port printable)
|
|
(gen-print* printable [port] #:width width #:height [height])
|
|
#:defaults ([string?
|
|
(define/generic super-print gen-print)
|
|
(define (gen-print s [port (current-output-port)])
|
|
(fprintf port "String: ~a" s))
|
|
(define (gen-port-print port s)
|
|
(super-print s port))
|
|
(define (gen-print* s [port (current-output-port)]
|
|
#:width w #:height [h 0])
|
|
(fprintf port "String (~ax~a): ~a" w h s))]))
|
|
|
|
(define-struct num (v)
|
|
#:methods gen:printable
|
|
[(define/generic super-print gen-print)
|
|
(define (gen-print n [port (current-output-port)])
|
|
(fprintf port "Num: ~a" (num-v n)))
|
|
(define (gen-port-print port n)
|
|
(super-print n port))
|
|
(define (gen-print* n [port (current-output-port)]
|
|
#:width w #:height [h 0])
|
|
(fprintf port "Num (~ax~a): ~a" w h (num-v n)))])
|
|
|
|
(define-struct bool (v)
|
|
#:methods gen:printable
|
|
[(define/generic super-print gen-print)
|
|
(define (gen-print b [port (current-output-port)])
|
|
(fprintf port "Bool: ~a"
|
|
(if (bool-v b) "Yes" "No")))
|
|
(define (gen-port-print port b)
|
|
(super-print b port))
|
|
(define (gen-print* b [port (current-output-port)]
|
|
#:width w #:height [h 0])
|
|
(fprintf port "Bool (~ax~a): ~a" w h
|
|
(if (bool-v b) "Yes" "No")))])
|
|
|
|
(define x (make-num 10))
|
|
(gen-print x)
|
|
(gen-port-print (current-output-port) x)
|
|
(gen-print* x #:width 100 #:height 90)
|
|
|
|
(gen-print "Strings are printable too!")
|
|
|
|
(define y (make-bool #t))
|
|
(gen-print y)
|
|
(gen-port-print (current-output-port) y)
|
|
(gen-print* y #:width 100 #:height 90)
|
|
|
|
(define/contract make-num-contracted
|
|
(-> number?
|
|
(printable/c
|
|
[gen-print (->* (printable?) (output-port?) void?)]
|
|
[gen-port-print (-> output-port? printable? void?)]
|
|
[gen-print* (->* (printable? #:width exact-nonnegative-integer?)
|
|
(output-port? #:height exact-nonnegative-integer?)
|
|
void?)]))
|
|
make-num)
|
|
|
|
(define z (make-num-contracted 10))
|
|
(eval:error (gen-print* z #:width "not a number" #:height 5))
|
|
]
|
|
|
|
@defform[(generic-instance/c gen-id [method-id method-ctc] ...)
|
|
#:contracts ([method-ctc contract?])]{
|
|
|
|
Creates a contract that recognizes structures that implement the @tech{generic
|
|
interface} @racket[gen-id], and constrains their implementations of the
|
|
specified @racket[method-id]s with the corresponding @racket[method-ctc]s.
|
|
|
|
}
|
|
|
|
@defform[(impersonate-generics gen-id val-expr
|
|
[method-id method-proc-expr] ...
|
|
maybe-properties)
|
|
#:grammar ([maybe-properties code:blank
|
|
(code:line #:properties props-expr)])
|
|
#:contracts ([method-proc-expr (any/c . -> . any/c)]
|
|
[props-expr (list/c impersonator-property? any/c ... ...)])]{
|
|
|
|
Creates an @tech{impersonator} of @racket[val-expr], which must be a structure
|
|
that implements the @tech{generic interface} @racket[gen-id]. The impersonator
|
|
applies the results of the @racket[method-proc-expr]s to the structure's implementation
|
|
of the corresponding @racket[method-id]s, and replaces the method
|
|
implementation with the result.
|
|
|
|
A @racket[props-expr] can provide properties to attach to the
|
|
impersonator. The result of @racket[props-expr] bust be an list with
|
|
an even number of elements, where the first element of the list is an
|
|
impersonator property, the second element is its value, and so on.
|
|
|
|
@history[#:changed "6.1.1.8" @elem{Added @racket[#:properties].}]}
|
|
|
|
|
|
@defform[(chaperone-generics gen-id val-expr
|
|
[method-id method-proc-expr] ...
|
|
maybe-properties)]{
|
|
|
|
Like @racket[impersonate-generics], but
|
|
creates a @tech{chaperone} of @racket[val-expr], which must be a structure
|
|
that implements the @tech{generic interface} @racket[gen-id]. The chaperone
|
|
applies the specified @racket[method-proc]s to the structure's implementation
|
|
of the corresponding @racket[method-id]s, and replaces the method
|
|
implementation with the result, which must be a chaperone of the original.
|
|
|
|
}
|
|
|
|
@defform[(redirect-generics mode gen-id val-expr
|
|
[method-id method-proc-expr] ...
|
|
maybe-properties)]{
|
|
|
|
Like @racket[impersonate-generics], but
|
|
creates an @tech{impersonator} of @racket[val-expr]
|
|
if @racket[mode] evaluates to @racket[#f], or creates
|
|
a @tech{chaperone} of @racket[val-expr] otherwise.
|
|
|
|
}
|
|
|
|
@close-eval[evaluator]
|