3.1 Defining Classes and Instances
A class is an abstract collection of names with optional default values.
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.
The class form declares an abstract class and its members.
> (class Eq [== (.. not /=)] [/= (.. not ==)] minimal ([==] [/=]))
Using an uninstantiated member is a syntax error.
> == eval:43:14: ==: no instance for Eq
in: ==
> (/= 1 0) eval:44:14: /=: no instance for Eq
in: (/= 1 0)
The instance form produces an object that holds class member definitions.
> (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"))) #t
> (with-instance NumberEq (and (== 1 1) (/= 2 3))) #t
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)))) #t
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))) #t
syntax
(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.
> (class Continuation [call] [abort (φ x (error (format "abort: ~a" x)))] minimal ([call])) > (call) eval:127:16: call: no instance for Continuation
in: (call)
> (abort 'no-op) eval:128:16: abort: no instance for Continuation
in: (abort (quote no-op))
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))
syntax
(instance class-id maybe-extends [member-id def-expr] ...+)
maybe-extends =
| 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")]))
syntax
(with-instance instance-id/optional-prefix expr ...+)
syntax
(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)))) 'START
'ESCAPE
> (with-instance CurrentContinuation (call (λ () (abort 1 2) 3)))
1
2
syntax
syntax
> (splicing-with-instance EscapeContinuation (define (f x) (call (λ () (abort `(ESC ,x)))))) > (f 1) '(ESC 1)
> (splicing-with-instance [C: CurrentContinuation] (define (g x) (C:call (λ () (C:abort `(ABORT ,x)))))) > (g 2) '(ABORT 2)
syntax
(instantiate instance-id)
(instantiate prefix instance-id)
> (module inst algebraic/racket/base (class Eq [== (.. not /=)] [/= (.. not ==)] minimal ([==] [/=])) (define-syntax EqEq (instance Eq [== eq?])) (define-syntax StringEq (instance Eq [== string=?])) (instantiate EqEq) (instantiate S: StringEq) (== 'A 'A) (S:/= "a" "b")) > (require 'inst)
#t
#t