If you have a bunch of structs with common properties or functions (in the English sense, not the CS sense), it makes sense to define a parent type (or base type) encoding those common properties and functions, and have other types inherit those properties and functions from the parent.
If type A inherits from type B, then A is a subtype (or child type) of B. A subtype is a "more specific" version of its parent type.
Defining a subtype
Here is the syntax for declaring a subtype:
(define-struct (<Subtype> <ParentType>) [...<SubtypeProperties>...] ...<SubtypeMethods>...)
Subtypes are still structs blah blah
So we can do all the usual things
Make a new subtype instance
(make-<Subtype> <ParentProperty1> ... <SubtypeProperty1> ...)
If you want to make a new instance of a type, we use
make-<Type> as usual, passing in all values for all of the struct's properties in declaration order.
With subtypes, the order of arguments goes:
- Parent properties, in declaration order
- Child properties, in declaration order
; Person is our base type (define-struct person [name age]) ; Friend is a subtype of Person (define-struct (friend person) [nickname]) (make-friend "Barack Obama" 55 "Barockstar") ; ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ ; parent props child props ; WRONG: Putting child props first. (make-friend "Barockstar" "Barack Obama" 55)
Check if something is a subtype
Same as before.
Getting and setting properties on a subtype
; Getting properties (<Type*>-<Property> <InstanceOfType>) ; Setting properties (set-<Type*>-<Property>! <InstanceOfType> <NewPropertyValue>)
Type* mean in the above?
In general, we use the same syntax of
<Type>-<Property> as before. The only added wrinkle is choosing which
Type goes on the left side of the hyphen, the child or the parent.
The answer is always to use the type where the property you're getting/setting was originally defined.
; Person is our base type (define-struct person [name age]) ; Friend is a subtype of Person (define-struct (friend person) [nickname]) (define obama (make-friend "Barack Obama" 55 "Barockstar")) ; GOOD: `nickname` is defined on the `friend` type (friend-nickname obama) ; BAD: `age` is defined on the `person` type, not the `friend` type (friend-age obama) ; (person-age obama) ; GOOD: `name` is defined on the `person` type (set-person-name! obama "Barack H. Obama II") ; BAD: `nickname` is defined on the `friend` type (set-person-nickname! obama "Number 44") ; (set-friend-nickname! obama "Number 44")
Methods on inherited types work the same way they normally do. Remember, you don't precede a method name with the usual
(define-struct type [...] #:methods (define (method t) ...)) ; No need to write `type-method` (method (make-type ...)) (define-struct (subtype type) [...] #:methods (define (method s) ...) (define (another s) ...)) ; No need to use `type-method` or `subtype-method` (method (make-subtype ...)) (another (make-subtype ...))