222: Compound Objects

by John Cowan (text), Arvydas Silanskas (implementation)

Status

This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-222@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

Compound objects are analogous to R6RS compound conditions, and are suitable for use in creating and handling conditions on non-R6RS systems, among other purposes. They encapsulate an immutable sequence of subobjects, which can be any object except another compound object. It is possible to implement R6RS compound conditions on top of compound objects, but not vice versa. Note that this SRFI does not provide any analogue to R6RS simple conditions, which are just records.

Rationale

Compound objects serve as a kind of poor man's multiple inheritance without the usual complications of multiple inheritance. A compound object can be used to represent multiple otherwise unrelated aspects of a value or situation. Because they are sequences, they can be used to represent priorities of interpretation from higher to lower. Most of the operations described in this section treat a non-compound object identically to a compound object with the simple object as its sole component.

Specification

;; The following definitions are referenced in later examples

(define-record-type <student>
  (student admission-year gpa)
  student?
  (admission-year admission-year)
  (gpa gpa))       ; grade-point average

(define-record-type <teacher>
  (teacher hire-year salary)
  teacher?
  (hire-year hired)     ; integer year
  (salary salary))  ; annualized

(define alyssa (student 1986 4.0))

(define guy (teacher 1981 25000))

Procedures

(make-compound obj ...)

Create a compound object containing the objects in obj... in the specified order. If any object in obj... is itself a compound object, it is flattened into its subobjects, which are then added to the compound object in sequence. [R6RS analogue: condition]

;; These definitions are referenced in later examples

(define george
  (make-compound
    'teaching-assistant
    (student 1979 3.8)
    (teacher 1983 1000)))

(define (teaching-assistant? obj)
  (eq? obj 'teaching-assistant))

(compound? obj)

Returns #t if obj is a compound object, and #f otherwise. [R6RS analogue: condition?]

(compound? alyssa) => #f
(compound? george) => #t

(compound-subobjects obj)

If obj is a compound object, returns a list of its subobjects; otherwise, returns a list containing only obj. [R6RS analogue: simple-conditions]

(compound-subobjects alyssa) => (#<student>)
(compound-subobjects george) => (teaching-assistant #<student> #<teacher>)

(compound-length obj)

If obj is a compound object, returns the number of its subobjects as an exact integer. Otherwise, it returns 1.

(compound-length alyssa) => 1
(compound-length george) => 3

(compound-ref obj k)

If obj is a compound object, returns the kth subobject. Otherwise, obj is returned. In either case, it is an error if k is less than zero, or greater than or equal to (compound-length obj).

(compound-ref alyssa 0) => #<student>
(compound-ref george 2) => #<teacher>

(compound-map mapper obj)

If obj is a compound object, returns a compound object whose subobjects result from invoking mapper on each subobject of obj. Although the subobjects of the result are in the same order as the subobjects of obj, the order in which mapper is applied to them is unspecified. If any resulting subobject is itself a compound object, it is flattened into its subobjects, which are then added to the result in sequence.

If obj is not a compound object, returns a compound object whose only subobject is the result of applying mapper to obj.

(compound-map - (compound 1 2 3 4 5)) => #<compound: -1 -2 -3 -4 -5>

(compound-map->list mapper obj)

If obj is a compound object, returns a list whose elements result from invoking mapper on each subobject of obj. Although the elements of the result are in the same order as the subobjects of obj, the order in which mapper is applied to them is unspecified.

If obj is not a compound object, returns a list whose only element is the result of applying mapper to obj.

(compound-map->list - (compound 1 2 3 4 5)) => (-1 -2 -3 -4 -5)

(compound-filter pred obj)

Returns a compound object It contains the subobjects of obj that satisfy pred.

If obj is not a compound object, it returns a compound object. If obj satisfies pred, the only subobject of the result is obj. If obj does not satisfy pred, the result has no subobjects.

(compound-filter teacher? alyssa) => #<compound>
(compound-filter teacher? george) =>
  #<compound: #<teacher>>

(compound-predicate pred obj)

If obj satisfies pred, or is a compound object and at least one of its subobjects satisfies pred, then returns #t; otherwise returns #f. [R6RS analogue: condition-predicate]

(compound-predicate student? alyssa) => #t
(compound-predicate student? george) => #t
(compound-predicate teacher? george) => #t
(compound-predicate teacher? guy) => #t
(compound-predicate teaching-assistant? alyssa) => #f
(compound-predicate teaching-assistant? guy) => #f
(compound-predicate teaching-assistant? george) => #t

(compound-access predicate accessor default obj)

If obj satisfies predicate, accessor is applied to obj and the result is returned. Otherwise, if obj is a compound object, accessor is applied to the first subobject that satisfies obj, and the result is returned. Otherwise, default is returned. [R6RS analogue: condition-accessor]

(define (uni-member-hire-year obj)
  (compound-access teacher? hire-year #f obj))

(uni-member-hire-year alyssa) => #f
(uni-member-hire-year guy) => 1981
(uni-member-hire-year george) => 1983
(uni-member-hire-year (make-compound '(27 42 98) 'fire!)) => #f

Implementation

The sample implementation is available in the GitHub repository.

Acknowledgements

Thanks to the participants on the mailing list.

© 2021 John Cowan, Arvydas Silanskas.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler