Algebraic Racket’s classes are a lot like Haskell’s type classes without the typing constraints. They allow abstract design patterns like monads with do-notation to be expressed simply and in a generic way that makes sense for Racket.
Using an uninstantiated member is a syntax error.
eval:43:14: ==: no instance for Eq
> (/= 1 0)
eval:44:14: /=: no instance for Eq
in: (/= 1 0)
> (define-syntax StringEq (instance Eq [== string=?])) > (define-syntax NumberEq (instance Eq [/= (|| < >)]))
Attempting to define an incomplete instance is a syntax error.
> (define-syntax BadEq (instance Eq))
eval:58:35: instance: Not a minimal definition
in: (instance Eq extends ())
The with-instance form temporarily instantiates the members of an instance.
> (with-instance StringEq (and (== "abcde" "abcde") (/= "hello" "world")))
> (with-instance NumberEq (and (== 1 1) (/= 2 3)))
The with-instance form can prefix the names it binds so multiple instances of the same class can be used together.
> (with-instance [S: StringEq] (with-instance [N: NumberEq] (and (S:== "abc" "abc") (N:== 123 123))))
The with-instances form offers a cleaner syntax for working with multiple instances.
> (define-syntax EqEq (instance Eq [== eq?]))
> (with-instances (EqEq [S: StringEq] [N: NumberEq]) (and (== + +) (S:== "?" "?") (N:== 0 0)))
(class class-id member-decl-or-def ...+ maybe-minimal)
member-decl-or-def = [member-id] | [member-id def-expr] maybe-minimal =
| minimal ([member-id ...+] ...)
A class form with n members defines n+1 names:
class-id, a class descriptor that represents the class.
for each member-id, a transformer binding that raises a syntax error.
The minimal directive, when present, constrains valid instances to only those that provide at least one of the minimal member-id sets.
> (define-syntax BadContinuation (instance Continuation [abort void]))
eval:138:7: instance: Not a minimal definition
in: (instance Continuation extends () (abort void))
(instance class-id maybe-extends [member-id def-expr] ...+)
| extends (instance-id ...)
The extends directive, when present, recursively imports the members of the instance-ids and the instances they extend.
> (define current-esc (make-parameter #f)) > (define current-con (make-parameter #f))
> (define ((current-abort param msg) . xs) (if (param) ($ (param) xs) (error msg)))
> (define-syntax EscapeContinuation (instance Continuation [call (φ f (call/ec (φ esc (parameterize ([current-esc esc]) (f)))))] [abort (current-abort current-esc "no escape continuation")]))
> (define-syntax CurrentContinuation (instance Continuation [call (φ f (call/cc (φ con (parameterize ([current-con con]) (f)))))] [abort (current-abort current-con "no current continuation")]))
(with-instance instance-id/optional-prefix expr ...+)
(with-instances (instance-id/optional-prefix ...) expr ...+)
instance-id/optional-prefix = instance-id | [prefix-id instance-id]
If any prefix-ids are given, they are prepended to the names of the members defined by the corresponding instance-ids.
> (with-instance EscapeContinuation (call (λ () (println 'START) (abort 'ESCAPE) (println 'DONE))))
> (with-instance CurrentContinuation (call (λ () (abort 1 2) 3)))
> (splicing-with-instance EscapeContinuation (define (f x) (call (λ () (abort `(ESC ,x)))))) > (f 1)
> (splicing-with-instance [C: CurrentContinuation] (define (g x) (C:call (λ () (C:abort `(ABORT ,x)))))) > (g 2)