Related articles |
---|
Keyword arguments ok@cs.rmit.edu.au (Richard A. O'Keefe) (1997-01-22) |
Re: Keyword arguments ok@cs.rmit.edu.au (1997-01-25) |
From: | "Richard A. O'Keefe" <ok@cs.rmit.edu.au> |
Newsgroups: | comp.compilers |
Date: | 22 Jan 1997 00:14:22 -0500 |
Organization: | Compilers Central |
Keywords: | design |
There's a programming language for statistics called S, which has
keyword arguments pretty much like Common Lisp's, in that the
parameter profile of the callee is not available to the compiler when
it is compiling the caller.
There's a new redesign and reimplementation of S called R. Currently
it is interpreted. I've agreed to set up a project here to develop a
compiler for R. I've figured out how to handle a lot of things, but
Lisp-style keyword arguments have me baffled.
How can lisp-style keyword parameter passing be implemented efficiently?
Actually, S adds a couple of extra wrinkles.
(1) ALL parameters may be passed positionally or by keyword.
This is a "must have" for the users.
(2) You can abbreviate keyword parameter names: if the parameter is
"length.out" in the function, "length.out", "length", "len", or
even "l" may work in the call. This is a "must have" for the users.
(3) You can freely mix keyword and positional arguments. This has the
same effect as if all the positional arguments came before the
keyword ones. (S evaluates arguments lazily, even though it is
a mostly conventional imperative language, so the order in which
the arguments are _evaluated_ has nothing to do with the order in
which they are _passed_.) This doesn't look like a problem.
(4) S has an equivalent of "rest" parameters, and a function which
has a "..." parameter will accept any keywords. I am prepared to
expend slow magic on rest parameters in R, it can't be worse than
what S does now.
(5) Formal parameters can be given default expressions; parameters
with defaults can be omitted.
Here's my current idea:
(a) Sort the actual parameters so that the positional parameters
appear before the keyword parameters, and the keyword parameters
are in lexicographic order. This is legitimate because the order
of evaluation of arguments isn't defined (as mentioned above, in
S it's controlled by when the argument is first _used_, which may
vary from call to call of the same function).
(b) S and R have introspection facilities. Environments can be reified
and operated on. My current idea about that is to build environments
on a stack to simplify introspection, lazily copying them to the heap
when and only when included in heap structures. Because of this,
positional parameters will be passed on a stack in such a way that
they are already in the right place in the environment object.
(c) Plain function call, e0(e1,...,em)
evaluate e0
Prepare-Call
push e1
...
push em
Enter
"Prepare-Call" sets up the header for the new environment on the stack.
"Enter" expands the environment to have slots for all the parameters
and local variables and transfers control to the function (which then
basically does
if (is.missing(opt.par.i)) opt.par.i = default.value.for.pari.i
...
for all the optional parameters.
(d) Function call with keyword parameters, e0(e1,..,em,k1=v1,...,kn=vn)
evaluate e0
Prepare-Call
push e1
...
push em
Allocate-Rest-Of-Environment
push v1
...
push vn
Enter-With-Keywords ["k1",...,"kn"]
"Allocate-Rest-Of-Environment" would do everything that "Enter"
does except transfer control. The values of the keyword parameters
would then _not_ be in the area comprising the new environment.
Thanks to the introspection facilities, an S or R function object
must contain the names of its parameters, and might as well contain
another copy in lexicographic order, so we can use a linear merge
to distribute the keyword parameter values to the appropriate slots.
I can't help feeling that there has to be a better way, but since you
can't know at compile time what the parameter names _are_, I don't
know what that better way might be.
[How about this: at compile time, make up an array of pointers with an
entry for each {caller, callee, arguments} triple used in a module,
e.g. "foo calls bar using zip, zorp, and two positionals", and make
the calls go through that array. Initialize each of those pointers to
point to a generic glue routine that constructs an argument
descrambler that puts stuff in the right places for the callee,
substitutes that descrambler in the pointer array and then jumps to
it. That way, there's a lot of interpretive overhead on the first
call but calls in loops, the ones that matter for performance, run
fast the second and later times. The calling sequence could be quite
simple, just push all the arguments and a pointer to a signature that
the generic glue would use to create the unscramber, but the unscrambler
would ignore. -John]
--
Return to the
comp.compilers page.
Search the
comp.compilers archives again.