Ciao,

  I  have implemented  a  syntactic layer  on  top of  (rnrs
records  procedural  (6)), which  allows  the definition  of
class types in a library and their use in another library by
importing only the class name and the general OOP macros:

  (library (defs)
    (export <alpha>)
    (import (classes))
    (define-class <alpha>
      (fields a b c)))

  (library (use)
    (export)
    (import (classes) (defs))
    (let ((o (make <alpha> 1 2 3)))
      ---))

I  have  taken the  idea  on how  to  do  it from  Sperber's
reference implementation of  the syntactic records layer for
R6RS (the class name <alpha> is bound to a syntax, etc).

  With  this step  I  think  it is  possible  to write  some
OOP-looking stuff;  I would  be glad if  some kind  soul can
comment on the syntaxes I am using for my classes library.

  In what follows I  am using (nausicaa) as language, rather
than  (rnrs), to  make things  simpler to  read.  (nausicaa)
reexports  all the  bindings  from (rnrs),  plus some  other
utilities,  plus it  exports  a version  of DEFINE,  LAMBDA,
CASE-LAMBDA and  the LET variants which  support class types
specifications; when the LET syntaxes detect that no type is
selected, they  default to the LET bindings  from (rnrs), so
there should be no performance penalty in untyped code.

  I  will  not  illustrate  all the  features  and  syntaxes
detail, only a  brief introduction and some of  the things I
still have to decide.

  If someone wants to try the current version:

<http://github.com/downloads/marcomaggi/nausicaa/nausicaa-0.2d2-src.tar.bz2>

it works with  Ikarus, Petite Chez and the  latest Mosh from
its  repository's master branch;  Ypsilon support  is broken
for the known hygiene problems; Larceny may work, not tested
yet (it takes forever to compile).


Dot notation
------------

The main reason for the  existence of the classes library is
to allow dot notation to access fields and methods; the core
syntax is WITH-CLASS:

  (import (nausicaa))

  (define-class <alpha>
    (fields a b c)
    (method (d self)
      4))

  (let ((o (make <alpha>
             1 2 3)))
    (with-class ((o <alpha>))
      (list o.a o.b o.c (o.d))))
  => (1 2 3 4)

the augmented  DEFINE, LAMBDA, CASE-LAMBDA  and LET variants
all  expand to  WITH-CLASS  forms which  in  turn expand  to
nested LET-SYNTAX  forms; the  same code with  the augmented
LET is:

  (import (nausicaa))

  (define-class <alpha>
    (fields a b c)
    (method (d self)
      4))

  (let (((o <alpha>) (make <alpha>
                       1 2 3)))
    (list o.a o.b o.c (o.d)))
  => (1 2 3 4)


On identifier syntaxes
----------------------

I  have   decided  that  I  am  going   to  accept  whatever
performance  penalty results  from  accessing record  fields
through identifier syntaxes.

  Classes  have  virtual fields  which  result  in calls  to
functions  or  expansion  of  macros;  I  will  put  in  the
documentation style notes  about what to do and  what not to
do; these will be:

(1) Identifier reference should have no side effects.

(2) Functions and  syntaxes invoked by identifier references
should never intentionally raise exceptions.

is there some other known bad practice?


Common vs knitting inheritance
------------------------------

Class  instances  are  regular  records from  (rnrs  records
procedural  (6)) and (classes)  uses the  single inheritance
implementation of  that library; what  is open to  decide is
how to use dot notation to access the type-specific members.

  Everybody  is  used   to  "common"  inheritance  in  which
subclass  members  override superclass  ones;  this is  what
happens when using the simple form of the INHERIT clause:

  (import (nausicaa))

  (define-class <alpha>
    (fields a b z))

  (define-class <beta>
    (inherit <alpha>)
    (fields c d z))

  ;;accessing fields of both class and superclass
  (let (((p <beta>) (make <beta>
                      1 2 3
                      4 5 6)))
    (list p.a p.b p.c p.d))
  => (1 2 4 5)

  ;;precedence to subclass fields
  (let (((p <beta>) (make <beta>
                      1 2 3
                      4 5 6)))
    p.z)
  => 6

  ;;custom precedence of superclass fields
  (let (((p <beta> <alpha>) ;the last takes precedence
         (make <beta>
           1 2 3
           4 5 6)))
    p.z)
  => 3

  ;;accessing both fields with the same name
  (let* (((p <beta>) (make <beta>
                       1 2 3
                       4 5 6))
         ((q <alpha>) p))
    (list q.z p.z))
  => (3 6)


  But,  through  methods  and  virtual  fields  it  is  also
possible    to   implement    "knitting"    inheritance:   a
do-it-yourself  way which  I  have NOT  yet implemented  but
would not be difficult to do.

  Basically,  the INHERIT  clause  would optionally  specify
that  dot notation for  superclasses must  not be  used; the
superclass fields  are still included in  the subclass.  The
following shows  how to achieve the same  result of "common"
inheritance with "knitting" inheritance:

  (import (nausicaa))

  (define-class <alpha>
    (fields a b z)
    (methods red))

  (define (<alpha>-red (o <alpha>))
    (list o.a o.b o.z))

  (define-class <beta>
    (inherit <alpha>
      (dry))
    (fields c d z)
    (virtual-fields (immutable a <alpha>-a)
                    (immutable b <alpha>-b))
    (methods (red <alpha>-red)))

  (let (((o <beta>) (make <beta>
                      1 2 3
                      4 5 6)))
    (list o.a o.b o.c o.d o.z (o.red)))
  => (1 2 4 5 6 (1 2 3))

  Knitting inheritance would allow fine selection of what is
accessible  and what  is not,  plus renaming  of  fields and
methods.  It would not be  difficult to add selectors to the
INHERIT clause to let only  fields or only virtual fields or
only methods pass through.

  Would knitting  inheritance be useful or just  make a mess
of things?


Methods as macros
-----------------

An  in-definition  method  is  automatically expanded  to  a
function definition:

  (define-class <alpha>
    (fields a b c)
    (method (d (self <alpha>))
      (display self.a)))

is equivalent to:

  (define-class <alpha>
    (fields a b c)
    (methods (d automatically-generated-id)))

  (define (automatically-generated-id (self <alpha>))
    (display self.a))

  External methods can be either functions or macros:

  (define-class <alpha>
    (fields a b c)
    (methods (d <alpha>-d)
             (e <alpha>-e)))

  (define (<alpha>-d self)
    ---)

  (define-syntax <alpha>-e
    (syntax-rules ()
      ((_ ?self)
       ---)))

  It  should  be  possible  to  have  in-definition  methods
expanded to syntaxes:

  (define-class <alpha>
    (fields a b c)
    (method d
      (syntax-rules ()
        ((_ ?self)
         ---)))
    (method e
      (lambda (stx) ;macro transformer
        ---)))

but would this syntax be "beautiful" and comprehensible?

TIA
-- 
Marco Maggi

Reply via email to