This is the mail archive of the guile@cygnus.com mailing list for the guile project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: mildly incompatible change


> ** The semantics of smob marking have changed slightly.
> 
> The smob marking function (the `mark' member of the scm_smobfuns
> structure) is no longer responsible for setting the mark bit on the
> smob.

[cut]

I have never written a smob type or (for that matter) used Guile much
at all.  (I read this list mainly out of an interest in languages and
translators.)  But I will claim some expertise, because I once
implemented the equivalent of smobs for Emacs Lisp.  I was therefore
curious to see whether smobs had a similar interface to the one I
devised.  Here is what I found in smob.h (19980419):

    typedef struct scm_smobfuns
    {
      SCM (*mark) SCM_P ((SCM));
      scm_sizet (*free) SCM_P ((SCM));
      int (*print) SCM_P ((SCM exp, SCM port, scm_print_state *pstate));
      SCM (*equalp) SCM_P ((SCM, SCM));
    } scm_smobfuns;

I assume this defines an "abstract base class" for deriving new Scheme
pseudo-types whose methods are implemented in C.

My ELisp smobs (which I called "foreign objects") had a similar
virtual table structure, but with seven methods instead of four.  I'll
describe them below; perhaps you would comment on the desirability or
feasibility of adding some of them to the smob interface.

>From my lisp.h:

    #ifdef LISP_FOREIGN_OBJECT_TYPE
    /* struct Lisp_Foreign_Object can hold arbitrary binary data not directly
       managed by Lisp.  The VPTR points to a table of object methods that
       are called at various times.

       The TYPE-OF method is called by Ftype_of() and must return the same
       (`equal') object each time it is called with a given argument.

       DESTROY is called after the sweep phase of garbage collection, when
       the object is to be freed.

       The MARK and SWEEP methods may be called during the mark and sweep
       phases, repectively.  MARK must recursively call mark_object() on all
       contained Lisp objects.  SWEEP is called when our object is not
       referenced by other data, but before it is to be destroyed.  If SWEEP
       returns `nil', the object is not subsequently destroyed.

       TO_STRING is used by the printer and must return a string.  All
       foreign objects are printed in hash notation.

       EQUAL is used in the recursive equality tester.  It should pass its
       DEPTH argument to recursive_equal() when comparing subobjects, to
       guard against circularity.  EQUAL may assume that the `type-of' both
       args is the same (`equal', not `eq').  (Under the current
       implementation, the VPTRs will be the same, too.)  EQUAL must return
       a boolean value indicating whether its first two arguments are `equal'.

       The CALL method is called when our object appears in functional
       position.

       Any of the function pointers may be 0, in which case a default behavior
       will be used.

       Each of the methods corresponds to and is called only from within one
       enumeration of complex Lisp data types.  (Typically a switch
       statement in C code.)  The "foreign" object type effectively extends
       these enumerations, and thus the typespace, ad infinitum.  */

    struct Lisp_Foreign_Object_VTable
      {
	Lisp_Object (*type_of)   P_ ((Lisp_Object self));
	void        (*destroy)   P_ ((Lisp_Object self));
	void        (*mark)      P_ ((Lisp_Object self));
	Lisp_Object (*sweep)     P_ ((Lisp_Object self));
	Lisp_Object (*to_string) P_ ((Lisp_Object self));
	int         (*equal) P_ ((Lisp_Object self, Lisp_Object other, int depth));
	Lisp_Object (*call) P_ ((Lisp_Object self, int nargs, Lisp_Object *args));
      };

    struct Lisp_Foreign_Object
      {
	int type : 16;	/* = Lisp_Misc_Foreign_Object */
	int spacer : 16;
	struct Lisp_Foreign_Object_VTable *vptr;
	void *data;
	Lisp_Object lisp_data;
      };

    #endif /* LISP_FOREIGN_OBJECT_TYPE */


I'll assume that your `mark' corresponds to my `mark', your `equalp'
corresponds to my `equal', and `print' and `to_string' are roughly
equivalent.

At first, I used a single `destroy' the way I assume the smob `free'
function works.  Later, I realized that the destructor might want to
call Lisp code, which shouldn't happen while any mark bits are set.
So I split object destruction into `sweep', which is called during
gc_sweep(), and `destroy', called when it is safe to invoke user
code.

It was also useful to allow the sweep method to return a value
indicating that the object was still referenced implicitly and should
not be freed yet.  (I see the smob `free' returns a value; can that be
used to prevent destruction?)

The `type_of' method exists because there are conceptually different
types of foreign object, which may share the same virtual table.  I
called it from the Lisp builtin `type-of' function (not sure if
there's a Scheme counterpart) and when printing.  (If type_of was a
null pointer, you got #<foreign-object (output of to_string)>, similar
to the way I imagine smobs are printed.)

Finally, I added `call' to simplify the conversion of foreign code
objects.  Originally, when wrapping code objects, I constructed a
lambda expression that passed the foreign object and its arguments to
a special subr designed for the purpose.  This was inelegant, so I
allowed objects to define a `call' method, putting them on a par with
the `subr' type.  Of course, this required a change to the evaluator.

Am I right in my assumptions about the smob interface?  Would you like
me to take a stab at documenting smobs (if it hasn't been done)?

Cheers
-John