Gauche Devlog

< About this blog | Better REPL? >


ERR5RS Records and beyond

Release 0.9.1 will include support for ERR5RS Records (srfi:99). It also supersedes the existing srfi-9 records (srfi:9).

The srfi-9 record implementation as of Gauche 0.9 is just a thin wrapper of Gauche's class facility. That is, a record type that has slot x, y, z is actually an ordinary Gauche class with slot x, y, z. It has no advantage to, and less features than Gauche's object system, except that it is portable.

In 0.9.1 I make records a bit more special. A record type is still a class, but using MOP, it is customized to take advantage of inflexibility of records---we can make it more efficient. So in some places records can be better choice than classes.

First of all, I don't make record types obey the class redefinition protocol. If you define a record type and bound to a variable which previously bound to another record type, then the variable is just overwritten; the old type and the new type don't have any relation; and instances of the old type remains as they are, instead of being upgraded to the new type.

This allows us to allocate a record instance as a flat array, instead of a header pointing to a value array. We can also pre-calculate offsets of slots for accessors and modifiers. I'm also planning to enable some aggressive inlining so that record accessors and modifiers are inline expanded to be as fast as vector access. (The inlining part has not implemented yet. But it is a fun MOP twiddling to adapt the record semantics into Gauche's class system).

Secondly, I extended ERR5RS and added a "pseudo record type", which treats other type of aggregates such as lists or vectors as if they are records. For Common Lisp users, it is like (:type list) or (:type vector) option of defstruct. Here's an example:

gosh> (use gauche.record)
gosh> (define-record-type (point (pseudo-rtd <vector>))
        #t #t (x) (y) (z))
gosh> (make-point 2 3 4)
#(2 3 4)
gosh> (point-x (make-point 2 3 4))
gosh> (let1 p (make-point 2 3 4) (point-z-set! p 9) p)
#(2 3 9)

The point record type inherits a pseudo record type returned from 'pseudo-rtd, which in this case a record type that uses a vector as a storage. The constructor make-point returns a vector. Accessors and modifiers takes a vector.

Besides the efficiency (vector access is very fast in Gauche), I see another advantage of this; it is less binding. Suppose your library defines concrete point record type, and ask users to send the data in points through API. If the user module have different representation of points, it have to convert every points back and forth to call your library. If your library exposes points with pseudo type, on the other hand, the user can simply use vectors; which can be interpreted in may other ways.

Currently I'm only testing with vector-backed records, but the plan is also enable uniform-vector backed ones---then it will be more attractive since uniform vectors can be directly passed into GL layer (via Gauche-gl).

Finally, a small addition to the srfi; I made the record accessor work with generalized set!, if the slot is mutable.

gosh> (define-record-type pare #t #t (kar) (kdr))
gosh> (define p (make-pare 1 2))
gosh> (set! (pare-kar p) 100)
gosh> (pare-kar p)

Another idea I'm pondering is to define a default printer to records, so that it prints out its slot values something like CL's struct.

Tags: 0.9.1, gauche.record, srfi-9, srfi-99

Post a comment