Integrated R6RS record to CLOS

I have made a sort of huge change for Sagittarius in these couple of days and that is now R6RS record can be used with generic functions. So let me show what's the exact change for this.

Sagittarius had 2 type systems, one is CLOS and other one is R6RS record and these didn't have any compatibility. So following code were invalid.
(import (rnrs) (clos user))
(define-record-type (pare kons pare?)
  (fields (mutable x kar set-kar!)
          (immutable y kdr)))

(define-method write-object ((p pare) out) 
  (format out "#<pare ~s:~s>" (kar p) (kdr p)))

(print (kons 1 2))
This was because R6RS record didn't create CLOS class but own record type. I was thinking this is very inconvenient and made me not to use R6RS record. So I have made the change and now above code works as I expect.

Followings are what I've changed internally so may not be so interesting.

[Slot ordering and shadowing]
I needed to change computed slot order of a class from subclass slots followed by super-class slots to super-class slots followed by subclass slots. And to make R6RS record spec satisfied, made not to shadow any duplicated slots. Following code describes a lot;
(import (rnrs) (clos user) (clos core))

(define-class <a> () (a b))
(define-class <b> (<a>) (c d))
(define-class <c> (<a>) (a c d))

(print (class-slots <b>))
;; ((c) (d) (a) (b)) : 0.5.0 or before
;; ((a) (b) (c) (d)) : current

(print (class-slots <c>))
;; ((a) (c) (d) (b))     : 0.5.0 or before
;; ((a) (b) (a) (c) (d)) :current
So basically no eliminating slots and different order. Then I had immediately noticed the problem that this breaks slot accessing. For example, refering <c>'s slot 'a' may return <b>'s 'a' slot value. The solution was very easy. The bottom most class's slots need to be shown first this means searching reverse order was sufficient.

The benefit of this change is big. Accessing slot with class is now always returns proper position of slot. Slot accessor contains index of the slot position and the change made this position always the same no matter how many you extend classes. For above example, position of class <a>'s slot 'a' is always 0 and before this wasn't always 0 (obviously...). Additionally, slot accessor also contains the class information that indicates on which class it's defined.

[Defining procedual layer in Scheme]
I have made a small footprint for this integration with CLOS. And based on this code, I've implemented procedural layer in Scheme world so that those ugly C code for record could be removed.

The Scheme implementation creates a CLOS class per record type descriptor(RTD) and set it to RTD and visa versa. This could save me from a lot of troubles such as refering record constructor descriptor (RCD) from record type. (I think there is a better solution but I was lazy.) There is a small problem with current Scheme code, that is it is impossible to create more than 1 record constructor descriptor from one record type descriptor. I may fix this if it will be a problem but highly doubt it.

[Record type meta class]
To distinguish between normal CLOS instance and record instance, I needed to create an additional meta class for it. However, it was pain in the ass to create meta class in C level so I decided to extend current class structure to have RTD and RCD fields and not to show the slot in but . This makes memory efficiency slightly bad (2 words) but I don't think it's a big deal.


Shiro Kawai said...

The slot ordering/shadowing policy can be customized by MOP, so I'm curious why you chose to change the default behavior and deviate from CLOS. Besides, the slot position may change anyway if the class is redefined.

kei said...

It's based on 2 reasons;

1. compute-slots is not specialised so often and adding specialised method makes a bit of performance issue.

2. (partially on the article but) C level memory layout. Previous implementation could not set slot value properly using class.
;; assume the class has sub class(es)
(slot-set-using-class class obj slot value) ;; -> may not be the ideal position

The latter reason was critical to integrate with R6RS record. (There might be a way to do this without changing slot position but couldn't find.) Class redefinition may change the slot position however it simply allocates and computes all slots again (and set if the original slots are unbounded), thus all slots are most likely the same order so I don't think this may cause a problem.

Shiro Kawai said...

compute-slots is called only once per record-type definition, so usually I don't think that's a performance issue. More critical performance benefit from fixed slot position can be achieved if you embed slot position in slot accessors (instead of accessing slots by name), which can be a good reason to support it.

I don't know exactly why the default CLOS slot policy (merge slot definitions of the same name) is adopted, but I can think of a few reasons; subclasses can modify slots, and many utilities that uses reflection to deal with arbitrary classes don't assume there are duplicates in slot names (suppose a serializer library that emits slot-name and value pairs).

kei said...

The slot accessor has the position so I needed to make sure that it needs to set a value to the same position which class of slot accessor users use.

That's for me the weird behaviour of CLOS's slot. As long as an object is an instance of class A (suppose this is a subclass of class B and both contains the same slot name X), then accessing the object's slot X, I think, should be done by under the definition of class A. So for me there was no reason to merge slot other than the sake of a bit of memory efficiency. And couldn't think of the reason why users want class B's slot X to be implicitly indicates the same position of class A's X. (personally, I wanted it to be separate so that users can explicitly accesso to class B's X.)

Shiro Kawai said...

Oh, I realized my mistake in the previous comment. With CLOS' default policy, subclass can modify slot *options* of the slots inherited from the superclass. And don't forget that CLOS has multiple inheritance; if you fix slot positions you get the same problem as C++ multiple inheritance. (RnRS records only allows single inheritance, so it makes sense to fix slot positions for them.)

kei said...

Ah, that makes sence (I don't like that policy though). I totally forgot about multi inheritance. I may face the problem related to this.

Post a Comment