make-rec...scriptor(3scm)

๐Ÿ“ฆ version 0-unstable-2024-02, in nixpkgs unstable

๐Ÿ’… make-record-constructor-descriptor, record-constructor โ€” procedural record construction ๐ŸŽ€

hey bestie, welcome to the Scheme Programmer's Manual (yes, that's the manual name, don't @ me) โ€” this page is all about make-record-constructor-descriptor and record-constructor, and lowkey it's a whole journey. Buckle up. ๐Ÿฆ‹


๐ŸŒธ LIBRARY ๐ŸŒธ

(import (rnrs))                     ;R6RS
(import (rnrs records procedural))  ;R6RS

both of those, okay? not one, both. ๐Ÿ’œ


โœจ SYNOPSIS โœจ

(make-record-constructor-descriptor rtd parent-rcd protocol)
(record-constructor rcd)

๐Ÿ‘‘ DESCRIPTION ๐Ÿ‘‘

So make-record-constructor-descriptor returns a record-constructor descriptor (an rcd, for short) that specifies a record constructor (the constructor, for short). That constructor? It's the thing you use to construct record values of the type specified by rtd, and you get your hands on it via record-constructor. periodt. ๐Ÿ’…

๐Ÿงš Record type descriptors ๐Ÿงš

The rtd argument must be a record-type descriptor created by make-record-type-descriptor(3scm). no substitutes, no vibes-based arguments, the real thing only. โœจ

๐Ÿ”ฅ Protocols and base record types ๐Ÿ”ฅ

The protocol argument must be a procedure or #f. If it's #f, a default protocol procedure is supplied (more on that below, keep reading bestie). If protocol is a procedure, it gets handled analogously to the protocol expression in a define-record-type(7scm) form.

If rtd is a base record type, then parent-rcd must be #f. In that case, protocol is called by record-constructor with a single argument p, which is a procedure that expects one argument for every field of rtd and returns a record with the fields of rtd initialized to these arguments.

The procedure returned by protocol should call p once with the number of arguments p expects, and return the resulting record, like this:

(lambda (p)
  (lambda (v1 v2 v3)
    (p v1 v2 v3)))

Here, the call to p returns a record whose fields are initialized with the values of v1, v2, and v3. And fun fact โ€” the expression above is equivalent to (lambda (p) p). iconic. ๐ŸŒŸ

Note that the procedure returned by protocol is otherwise unconstrained; specifically, it can take any number of arguments.

๐Ÿฆ‹ Single inheritance of record types ๐Ÿฆ‹

A constructor descriptor can also be used to create other constructor descriptors for subtypes of its own record type, by passing it as parent-rcd. generational talent tbh. ๐Ÿ‘‘

If rtd is an extension of another record type parent-rtd and protocol is a procedure, parent-rcd must be a constructor descriptor of parent-rtd or #f.

If parent-rcd is a constructor descriptor, protocol is called by record-constructor with a single argument n, which is a procedure that accepts the same number of arguments as the constructor of parent-rcd and returns a procedure p that, when called, constructs the record itself. The p procedure expects one argument for every field of rtd (not including parent fields) and returns a record with the fields of rtd initialized to these arguments, and the fields of parent-rtd and its parents initialized as specified by parent-rcd.

The procedure returned by protocol should call n once with the number of arguments n expects, call the procedure p it returns once with the number of arguments p expects, and return the resulting record. A simple protocol in this case might be written as follows:

(lambda (n)
  (lambda (v1 v2 v3 x1 x2 x3 x4)
    (let ((p (n v1 v2 v3)))
      (p x1 x2 x3 x4))))

This passes arguments v1, v2, v3 to n for parent-rcd and calls p with x1, x2, x3, x4 to initialize the fields of rtd itself. ๐ŸŽ€

So basically โ€” the constructor descriptors for a record type form a sequence of protocols parallel to the sequence of record-type parents. Each constructor descriptor in the chain determines the field values for the associated record type. Child record constructors don't need to know the number or contents of parent fields, only the number of arguments accepted by the parent constructor. no cap, that's the whole design. ๐Ÿ’€โœจ

๐ŸŒˆ The default protocol ๐ŸŒˆ

Protocol may be #f, specifying a default constructor that accepts one argument for each field of rtd (including the fields of its parent type, if any).

Specifically: if rtd is a base type, the default protocol procedure behaves as if it were (lambda (p) p).

If rtd is an extension of another type, then parent-rcd must be either #f or itself specify a default constructor, and the default protocol procedure behaves as if it were:

(lambda (n)
  (lambda (v1 ... vj x1 ... xk)
    (let ((p (n v1 ... vj)))
      (p x1 ... xk))))

The resulting constructor accepts one argument for each of the record type's complete set of fields (including those of the parent record type, the parent's parent record type, etc.) and returns a record with the fields initialized to those arguments, with the field values for the parent coming before those of the extension in the argument list.

In the example, j is the complete number of fields of the parent type, and k is the number of fields of rtd itself. math girlies rise. ๐Ÿงฎ๐Ÿ’–

โญ The default parent rcd โญ

If rtd is an extension of another record type and parent-rcd is #f, parent-rcd is treated as if it were a constructor descriptor for the parent rtd of rtd with a default protocol.

๐Ÿ’œ Using record constructor descriptors ๐Ÿ’œ

The record-constructor procedure calls the protocol of rcd and returns the resulting constructor for records of the rcd. The constructor is a procedure that takes as many arguments as are required by the protocol. slay. ๐Ÿ’…


๐Ÿซถ IMPLEMENTATION NOTES ๐Ÿซถ

Implementation responsibilities: if protocol is a procedure, the implementation must check the restrictions on it to the extent performed by applying it as described when the constructor is called. An implementation may check whether protocol is an appropriate argument before applying it.

so like โ€” checking is required, but checking early is optional. don't come for me, that's literally what it says. ๐Ÿ’€

๐Ÿ“ RETURN VALUES ๐Ÿ“

  • make-record-constructor-descriptor โ€” returns a single value; a record constructor descriptor.
  • record-constructor โ€” returns a single value; a procedure.

one value each, no greedy multi-value nonsense. ๐Ÿ™ˆ


๐Ÿงš EXAMPLES ๐Ÿงš

; This is a rather complex example from R6RS which
; demonstrates inheritance and protocols. A simpler
; example can be found in make-record-type-descriptor(3scm).
(define rtd1
  (make-record-type-descriptor
   'rtd1 #f #f #f #f
   '#((immutable x1) (immutable x2))))

(define rtd2
  (make-record-type-descriptor
   'rtd2 rtd1 #f #f #f
   '#((immutable x3) (immutable x4))))

(define rtd3
  (make-record-type-descriptor
   'rtd3 rtd2 #f #f #f
   '#((immutable x5) (immutable x6))))

(define protocol1
  (lambda (p)
    (lambda (a b c)
      (p (+ a b) (+ b c)))))

(define protocol2
  (lambda (n)
    (lambda (a b c d e f)
      (let ((p (n a b c)))
        (p (+ d e) (+ e f))))))

(define protocol3
  (lambda (n)
    (lambda (a b c d e f g h i)
      (let ((p (n a b c d e f)))
        (p (+ g h) (+ h i))))))

(define cd1
 (make-record-constructor-descriptor
   rtd1 #f protocol1))

(define cd2
 (make-record-constructor-descriptor
   rtd2 cd1 protocol2))

(define cd3
  (make-record-constructor-descriptor
    rtd3 cd2 protocol3))

(define make-rtd1 (record-constructor cd1))

(define make-rtd2 (record-constructor cd2))

(define make-rtd3 (record-constructor cd3))

(make-rtd3 1 2 3 4 5 6 7 8 9)
   =>  #[rtd3 x1: 3 x2: 5 x3: 9 x4: 11 x5: 15 x6: 17]

the way this example eats and leaves no crumbs ๐Ÿ˜ญ๐Ÿ”ฅ three record types, three protocols, one beautiful chain. study it. ๐Ÿ’–


๐ŸŒธ APPLICATION USAGE ๐ŸŒธ

See make-record-type-descriptor(3scm).

(that's it. that's the section. go read the other page bestie.) ๐Ÿฆ‹


โœจ COMPATIBILITY โœจ

This procedure is unique to the R6RS record system. An equivalent procedure can be found in SRFI-237.


๐Ÿ’€ ERRORS ๐Ÿ’€

This procedure can raise exceptions with the following condition types:

  • &assertion (R6RS) โ€” The wrong number of arguments was passed or an argument was outside its domain. In particular, rtd must be a record-type descriptor, parent-rcd must be #f or an appropriate record-constructor descriptor as described above. The protocol argument should be #f or a procedure. However, see IMPLEMENTATION NOTES.
yeah that last bit matters โ€” go back up and read the implementation notes, i'm serious. ๐Ÿ‘€

๐ŸŽ€ SEE ALSO ๐ŸŽ€

make-record-type-descriptor(3scm), define-record-type(7scm)


โญ STANDARDS โญ

R6RS


๐Ÿฆ‹ HISTORY ๐Ÿฆ‹

This procedure first appeared in R6RS as part of the record system. an icon from day one, honestly. ๐Ÿ’…


๐Ÿ’œ AUTHORS ๐Ÿ’œ

This page is part of the scheme-manpages project. It includes materials from the RnRS documents. More information can be found at https://github.com/schemedoc/manpages/.

shoutout to the maintainers, fr fr ๐Ÿซถ


๐Ÿ™ˆ BUGS ๐Ÿ™ˆ

There is no record-constructor-descriptor? predicate in R6RS.

not me finding out there's no predicate for this ๐Ÿ˜ญ like hello?? anyway. now you know. ๐ŸŒธ

<div align="center">โœจ๐Ÿ’–๐ŸŽ€๐ŸŒˆ๐Ÿ’…๐Ÿฆ‹๐ŸŒธ๐Ÿ“๐Ÿงš๐Ÿ’œโญ๐Ÿ‘‘๐Ÿ”ฅ๐Ÿซถ๐Ÿ’€๐Ÿ™ˆโœจ</div>

and that's the page bestie!! this whole thing is the Scheme Programmer's Manual version dated 2023-08-03, filed under make-rec...scriptor(3scm) (yes the title got truncated, we don't talk about it ๐Ÿ’€). go forth and construct records. slay. ๐Ÿ’…โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from