Structural inheritance
An in-depth example of structural inheritance is available for download here (.rkt).
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
(<Subtype>? <any>)
Same as before.
Getting and setting properties on a subtype
; Getting properties
(<Type*>-<Property> <InstanceOfType>)
; Setting properties
(set-<Type*>-<Property>! <InstanceOfType> <NewPropertyValue>)
What does 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
Methods on inherited types work the same way they normally do. Remember, you don't precede a method name with the usual (<Type>-...
hyphenation.
(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 ...))