Title

define-record-lambda

Author

Joo ChurlSoo

Status

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

Abstract

This SRFI introduces a macro, DEFINE-RECORD-LAMBDA, that defines a set of procedures, that is, a group of constructors and a predicate. The constructors also make a group of procedures, namely record lambdas, that have no explicit field accessors and mutators. They can have various kinds of fields, such as common fields, required fields, optional fields, automatic fields, read-only fields, read-write fields, invisible fields, immutable fields, and virtual fields.

Changes from SRFI 100

This SRFI is based on SRFI 100. Unlike the define-lambda-object of SRFI 100:

  1. The inheritance property has been removed because it was ineffective and superfluous.
  2. The record lambda uses field indices or symbolized field names to identify fields according to the constructors.
  3. The field sequence has been changed.

Rationale

A record lambda created by a constructor procedure is a procedure whose first argument is a field index or symbolized field name that is used to identify fields. It depends on the constructor. The record lambda plays the role of the accessor and mutator of each field and makes the explicit or implicit accessors and mutators unnecessary. In addition, this reduces the role of the predicate procedure.

This macro works not only as DEFINE-RECORD-TYPE with required fields but also as DEFSTRUCT of Common Lisp with optional fields. Each field except required fields can be used as a procedure that changes the values of the other fields. As a result, the record lambda itself has data and methods as well as their accessors and mutators.

Specification

(define-record-lambda <group> <field spec>)

<field spec> --> <common field>* <required field>* <optional field>* <automatic field>*

<common field> -->
	|  (,@<field>  <default>)	;read-only    automatic common field
	| ((,@<field>) <default>)	;read-write   automatic common field
	| (',@<field>  <default>)	;immutable    automatic common field
	| (`,@<field>  <default>)	;invisible    automatic common field

<required field> -->
	|  <field>			;read-only    required field
	| (<field>)			;read-write   required field
	|  'field			;immutable    required field
	|  `field			;invisible    required field

<optional field> -->
	|  (<field>  <default>)		;read-only    optional field
	| ((<field>) <default>)		;read-write   optional field
	| ('<field>  <default>)		;immutable    optional field
	| (`<field>  <default>)		;invisible    optional field

<automatic field> -->
	|  (,<field>  <default>)	;read-only    automatic field
	| ((,<field>) <default>)	;read-write   automatic field
	| (',<field>  <default>)	;immutable    automatic field
	| (`,<field>  <default>)	;invisible    automatic field
	| (,,<field>  <default>)	;virtual      automatic field

The name of the first <constructor> is generated by prefixing "make-" to the group name, or by prefixing "make-" and postfixing "-by-name" to the group name. And the name of the second <constructor> is generated by appending "/s" to the name of the first <constructor>. While the record lambda that is constructed by the former uses field indices to identify the fields, the one constructed by the latter uses symbols. And the average time required to access a randomly chosen field is more for the record lambda that is constructed by the latter than for the one by the former.

The name of <predicate> is generated by adding a question mark ("?") to the end of the group name.

The <group> and <field> must be identifiers.

Each <default> is an <expression> that is evaluated in an environment in which the values of all the previous <field>s are visible.

The define-record-lambda form is a definition and can appear anywhere any other <definition> can appear. Each time define-record-lambda form is evaluated, a new group is created with distinct <group>, <constructor>, and <predicate> procedures.

The <group> is bound to a procedure of one argument. It has information on its <constructor>s, <predicate>, and <field>s.

The <constructor> is bound to a procedure that takes at least as many arguments as the number of <required field>s. Whenever it is called, it returns a record lambda of the <group>, namely a procedure. Its first argument must be a symbol of the same name as <field> or a field index whose name is composed of the group name and field name. The record lambda becomes an accessor procedure of each <field> in case of one argument and a mutator procedure of each <field> in case of two arguments where the second argument is a new field value.

The names of <field>s are used to access the <field>s. So they must be distinct. The read-write fields can be modified, whereas any attempt to modify the values of the read-only fields via mutators signals an error.

Note: The read-only fields are not immutable. Their values, for instance, can be modified by other fields whose values work like their mutators.

The <required field> is initialized to the first one of the remaining arguments. If there are no more remaining arguments, an error is signaled.

The initialization of the <optional field>s is done by two types of <constructor>s:
1. <make-group-name> constructor
The initialization method of <optional field>s is the same as that of <required field>s except that the field is bound to the <default> instead of signaling an error if there are no more remaining arguments.
2. <make-group-name-by-name> constructor
The name used at a call site for the corresponding <optional field> is a symbol of the same name as the <field>. The remaining arguments are sequentially interpreted as a series of pairs, where the first member of each pair is a field name and the second is the corresponding value. If there is no element for a particular field name, the field is initialized to the <default>.

The <common field>s are also <automatic field>s, and initialized to each corresponding <default> that is evaluated at the time the define-record-lambda form is evaluated, and the values are shared with all the record lambdas that are made by the constructors of the define-record-lambda form.

The other <automatic field>s except <virtual field>s are initialized to each corresponding <default> that is evaluated at the time the record lambda is made by a constructor. Each <default> of <virtual field>s is evaluated each time when the field is accessed.

The <invisible field>s are externally nonexistent fields, that is, the fields are invisible outside of the define-record-lambda form but accessible inside of it. On the contrary, the <immutable field>s and <virtual field>s are internally nonexistent fields, that is, the fields are visible outside of the define-record-lambda form but inaccessible inside of it.

The <predicate> is a predicate procedure that returns #t for record lambdas constructed by <constructor> or <constructor>s of a <group> and #f for everything else.

Examples

;; invisible & immutable field example
(define-record-lambda stack
  (`stack '())					    ;invisible optional
  (',push (lambda (s) (set! stack (cons s stack)))) ;immutable automatic
  (,,pop (if (null? stack)			    ;virtual   automatic
	     (error "null stack" stack)
	     (let ((s (car stack)))
	       (set! stack (cdr stack))
	       s))))

(define st (make-stack))
(st stack-stack)	-> error stack-stack: undefined
(st stack-pop)		-> error null stack ()
(st stack-push 2)	-> error invisible or immutable field
((st stack-push) "rose")
((st stack-push) "lily")
((st stack-push) "sunflower")
(st stack-pop)		-> "sunflower"
(st stack-pop)		-> "lily"
(st stack-pop)		-> "rose"
(st stack-pop)		-> error null stack ()

(stack 'constructor)	-> ((make-stack . make-stack-by-name)
			    (make-stack/s . make-stack-by-name/s))
(stack 'predicate)	-> stack?
(stack 'field)		-> ((total
			     (read-write-field ())
			     (read-only-field (push pop))
			     (invisible-field (stack)))
			    (types
			     (common-field ())
			     (required-field ())
			     (optional-field (stack))
			     (automatic-field (push pop))
			     (immutable-field (push))
			     (invisible-field (stack))
			     (virtual-field (pop))))
(stack 'index)		-> ((stack-push . 0) (stack-pop . 1))


;; virtual field example
(let ()
  (define-record-lambda circle
    ((radius) 1)					   ;read-write optional
    (',area! (lambda (a) (set! radius (sqrt (/ a 3.14))))) ;immutable  automatic
    (,,area (* 3.14 radius radius)))	       	 	   ;virtual    automatic

  (list (let ((c (make-circle)))	;index
	  (list (c circle-area)
		(c circle-radius)
		(begin ((c circle-area!) (* 4 (c circle-area))) (c circle-area))
		(c circle-radius)
		(begin (c circle-radius 10) (c circle-area))
		(c circle-radius)))
	(let ((c (make-circle/s)))	;symbol
	  (list (c 'area)
		(c 'radius)
		(begin ((c 'area!) (* 4 (c 'area))) (c 'area))
		(c 'radius)
		(begin (c 'radius 10) (c 'area))
		(c 'radius)))))
-> ((3.14 1 12.56 2.0 314.0 10) (3.14 1 12.56 2.0 314.0 10))


;; common field example
(let ()
  (define-record-lambda record
    (,@total-record 0)			;read-only automatic common
    (,@total-connect 0)			;read-only automatic common
    (,order-of-birth (begin		;read-only automatic
		       (set! total-record (+ 1 total-record))
		       total-record))
    (,individual-connect 0)		;read-only automatic
    (,,connect (begin			;virtual   automatic
		 (set! individual-connect (+ 1 individual-connect))
		 (set! total-connect (+ 1 total-connect))
		 'connect)))

  (let* ((record-1 (make-record/s))
	 (record-2 (make-record/s))
	 (record-3 (make-record/s))
	 (record-4 (make-record/s))
	 (record-5 (make-record/s)))
    (record-1 'connect) (record-1 'connect) (record-1 'connect)
    (record-1 'connect) (record-1 'connect) (record-2 'connect)
    (record-2 'connect) (record-2 'connect) (record-2 'connect)
    (record-3 'connect) (record-3 'connect) (record-3 'connect)
    (record-4 'connect) (record-4 'connect) (record-5 'connect)
    (map (lambda (o)
	   (list (list 'order-of-birth (o 'order-of-birth))
	   	 (list 'individual-connect (o 'individual-connect))
		 (list 'total-record (o 'total-record))
		 (list 'total-connect (o 'total-connect))))
	 (list record-1 record-2 record-3 record-4 record-5))))
->
(((order-of-birth 1) (individual-connect 5) (total-record 5) (total-connect 15))
 ((order-of-birth 2) (individual-connect 4) (total-record 5) (total-connect 15))
 ((order-of-birth 3) (individual-connect 3) (total-record 5) (total-connect 15))
 ((order-of-birth 4) (individual-connect 2) (total-record 5) (total-connect 15))
 ((order-of-birth 5) (individual-connect 1) (total-record 5) (total-connect 15)))

Sample Implementation

Sample implementations are provided based on both define-syntax (syntax-rules with syntax-case or explicit renaming macro) and define-macro. They're available both in the Github repo and in this .tgz file.

References

[R6RS]Michael Sperber, R. Kent Dybvig, Matthew Flatt, and Anton von Straaten: Revised(6) Report on the Algorithmic Language Scheme.http://www.r6rs.org
[SRFI 9]Richard Kelsey: Defining Record Type.http://srfi.schemers.org/srfi-9
[SRFI 100]Joo ChurlSoo: Define-lambda-object.http://srfi.schemers.org/srfi-100/

Copyright

Copyright © Joo ChurlSoo (2012).

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