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: difficulty of writing translators


Per Bothner counters:

> > Maybe I've been using perl too long :-) but I prefer to introduce an
> > "lvalue" object type and provide automatic lvalue->rvalue conversion
> > as the context demands.
> 
> The problem is that you have to do this conversion *everywhere*,
> except in lvalue contexts.

And except where the compiler knows an rvalue is expected, in which
case an rvalue would be passed.

>  Having first-class lvalue objects is
> fine - unless you want to have automatic dereferences to rvalues.
> The only language I know that did that was Algol68, and they had
> the luxury of static typing (and thus a better concept of
> "expected type").

Upon reflection, I can see that my proposal would require a simple
form of static typing, but only in order for code to be optimized
properly.

Functions would be allowed to have prototypes indicating whether each
arg is an lval, an rval, or any val, and likewise for the returned
object.  If the compiler knows all this information where the
procedure is defined and called, I don't think there would need to be
any additional run-time overhead.

> Propose something concrete and implementable.

Since you ask...

   (define (add-n-double x y)
	   (* 2 (+ x y)))

would be optimally typed as

   (define (rvalue (add-n-double (rvalue x) (rvalue y)))
	   (* 2 (+ x y)))

since it must receive and return rvalues.  In order to safely avoid
the runtime check for rvalue-ness, code that calls this function needs
to see a prototype, such as

   (prototype rvalue (add-n-double (rvalue x) (rvalue y)))

On the other hand, a function that operates on its argument should be
declared to take an lvalue:

   (define (lvalue (incr (lvalue x)))
	   (set! x (1+ x))
	   x)

For compatibility with existing code, in the absence of type
specifiers, all arguments are checked and converted to rvalues if
necessary.  Type specifiers are necessary for best optimization.  You
are allowed to redefine a public function only if the new version has
a compatible signature.

A function call's semantics are as follows:

   (f (g))

If f has been declared to take an lvalue arg, and g has been declared
to return an rvalue, this is an error.

If f has been declared to take any value, g's return value is passed
to f as-is.  A prototype for such an f looks like one of these:

   (prototype (f x))	     ;; or
   (prototype lvalue (f x))  ;; or
   (prototype rvalue (f x))

If the declared type of f's first arg matches g's return type, the
value is passed without change.

If g's return type is "any" (or, equivalently, if g lacks a
prototype), and f takes an rvalue, a run-time check and possible
conversion is performed.

If g's type is any and f takes an lvalue, a run-time check and
possible error occurs (since an rvalue returned by g cannot be
converted).

Finally, if g must return an lvalue and f takes an rvalue, a run-time
conversion occurs with no type-checking.

I am avoiding discussion of how to handle variable-length argument
lists, because I haven't though it through yet.

> Note I am also adding to Kawa first-class "location" ("reference")
> values, but they have to be explicitly dereferenced.

That sounds like something Guile could benefit from, independently of
automatic lvalues.

> > But, maybe that's because I don't understand.
> 
> That seems plasusible.  At least I suspect you don't understand your
> own proposal.

I fear, rather, that my proposal is so radical that an implementation
of it could no longer be called Scheme.

-John