[gameprogrammer] Re: More scripting: Multiple return values

  • From: Bob Pendleton <bob@xxxxxxxxxxxxx>
  • To: Gameprogrammer Mailing List <gameprogrammer@xxxxxxxxxxxxx>
  • Date: Tue, 06 Apr 2004 18:07:19 -0500

On Tue, 2004-04-06 at 12:36, David Olofson wrote:
> On Tuesday 06 April 2004 17.31, Bob Pendleton wrote:
> > On Tue, 2004-04-06 at 09:17, David Olofson wrote:
> > > Some more questions regarding RT scripting language design;
> > >
> > > * Does anyone have experience with game and/or real time
> > >   scripting with a language that support functions with
> > >   multiple return values?
> >
> > No, not those specific kinds of languages, but I do have experience
> > with languages that have multiple return values and have spent a
> > lot of time looking at the problem of developing languages that
> > support them.
> 
> Perfect! :-)
> 
> 
> > > * Are multiple return values seriously useful? Confusing?
> > >   Dangerous?
> >
> > They are rarely useful, very confusing, and slightly dangerous. It
> > is hard for a programmer to keep track of what is being returned.
> > That is especially true in languages with dynamic typing and a
> > variable number of return values. If you have static typing then
> > the compiler can check for type mismatches, which saves the
> > programmer from errors.
> 
> I don't have static typing at all (yet), so it looks like I should be 
> very careful with this stuff, or better, just drop it. Besides, 
> though I haven't hacked enough code to get into trouble yet, I can 
> see a number of ways to mess things up, and I have yet to find any 
> *really* good reasons to support multiple return values at all.
> 
> 
> > If you language has a vector data type then it is easy to have
> > functions that return variable length vectors that contain the
> > multiple values.
> 
> Actually, both arguments and return values are passed using low level 
> light weight LISTs already. (They don't own or manage the referenced 
> data, so they're not really useful as a language level type.)
> 
> There will be a real vector type eventually, possibly replacing the 
> lightweight LIST type, so it seems logical to just use that instead 
> when you *really* want multiple return values.
> 

I would just go with that. But, consider adding an associative vector.
One that can be indexed with strings as well as numbers. It is really
nice to be able to write x =v["x"].

> 
> > I prefer to use sets with a known enumeration of
> > values to handle returning multiple values. The enumeration
> > determines the possible return values and the set, once returned
> > can be asked for members by name. Members that are non-existent or
> > have a NULL value (depends on how sets are implemented) were not
> > returned. Or, you can a default value for each member of the set
> > and have the function just set the values it wants. A function
> > might look something like:
> >
> >     function point() returns [x = 0, y = 0, z = 0, tag = "nothing"]
> >     {
> >
> >             return x=10, y=30, tag="upper left corner";
> >     }
> >
> > But, then assignment gets nasty:
> >
> >     x, y, ..., tag = point();
> >
> > The values have to be assigned in order to match the value of
> > return values of the function and you need a syntax to skip over
> > values you don't want.
> 
> In my language, a list of comma separate expressions evaluates into a 
> LIST with one lvalue for each expression. It would be easy enough to 
> add a "void" keyword, and/or give an empty expression within a coma 
> separated list the same behavior as ignoring the return from a 
> function. (The caller allocates space for the return value, but 
> forgets about it after the function returns.)
> 

Makes sense!

> 
> [...]
> > > I currently have support for multiple return values. (Functions
> > > have a list of "return arguments" before the function name in
> > > declarations.) There is support for variable argument count, but
> > > the result count is fixed. (Hardwired at compile time.)
> > >
> > > I also have these function references we've been discussing the
> > > last few days, and obviously, these cause trouble since I can't
> > > tell how many return values to make room for by looking at the
> > > function declaration. :-/
> >
> > The reference points to the function. If you can call a function
> > you have to at least know the number or arguments it takes.
> > Therefore the reference must point to a function descriptor.
> > Therefore you can expand the function descriptor to contain the
> > full function signature including the return values so that it can
> > be properly executed.
> 
> Yes. What I meant was just that with references (as opposed to 
> constant/"hardwired" calls), you have to do all this at run time 
> instead of compile time. 

Oh, of course. 

> Without statically typed function 
> references, all I can do is have the "bytecode" and/or the VM's CALL 
> instruction check that the called function is compatible with the 
> provided arguments, and throw a runtime exception if it isn't.

Yeah, I understand. As an aside the idea of a "virtual machine" can be a
box that traps the mind. You start thinking that the operations need to
be like the operations in a "real" computer. But, they don't. You can
have a "call" byte code that checks parameter and return types at run
time. You can have another bytecode that calls OpenGL's glBegin()
function if you want.

I don't know if you are falling into that trap. Just want to point it
out in case.

> 
> Anyway, the problem with this in my current implementation is that I 
> normally allocate space for return values at compile time, just like 
> for expression evaluation and stuff like that. That is, the code 
> relies on hardcoded register indices, so I can't just insert some 
> variable size array in the middle and then go on executing VM. I'd 
> have to put the variable size result list above the calling 
> function's register frame or something.
> 
> Now, if a function can only ever return a single value, this problem 
> goes away. :-)

Sounds like the way to go :-)

> 
> 
> > > A related problem is caused by the fact that I've implemented the
> > > assignment operator ('=') as an actual operator. That means it's
> > > handled by the normal infix expression evaluator, which in turn
> > > means that I can't just look at the left hand subexpression to
> > > figure out how many arguments I want, in case the right hand
> > > subexpression happens to be a function call. (*If* that's
> > > actually desired, that is.)
> >
> > Solved above. The assignment operator can inspect the function
> > signature to determine what it is going to return.
> 
> Except that operators are only invoked *after* their arguments have 
> been calculated...

Yes, but you can have instructions that check the data type of a
parameter after it is pushed on the stack and before the call
instruction. You are designing the machine and the language, you can do
anything you want to do. You can include all the type information in the
code following the call instruction. Though when I did that I put the
information in a block of memory and just put a pointer to it after the
call instruction. That way it didn't get duplicated every time I called
a function.

>  Function and operator arguments can only be passed 
> by reference or by value. 

In your language there are only reference and value parameters. There
are other kinds of bindings in other languages. Ok, Ok, I know that was
a very picky comment. :-)

> To get around that, I'd have to remove the 
> "=" operator and replace it with a special grammar rule for 
> assignments - which is probably just as well, as "=" is the only 
> operator that treats the left hand term as write-only.

Yeah, that is a problem in a lot of languages. If assignment is an
operator it has to act like an operator. If it is a statement type, then
you can do all sorts of interesting things with its syntax.

> 
> Anyway, no big deal. I just though it was a good idea to make "=" just 
> another operator, but it seems like I was wrong.
> 

That is one of the great debates in language design. I prefer having
assignment be a statement. That keeps you from writing:

        if (x = NULL) 

and spending a week trying to figure out why you are getting a
segfault... when you meant:

        if (x == NULL) 

> 
> > > As usual, it's probably a good idea to decide which problems to
> > > solve before starting to solve them. ;-)
> >
> > In LISP... you just return a list of values if they are all the
> > same type or you return an associative list (which is pretty much a
> > set). Perl does it by returning vectors.
> 
> Yeah... I think I'll convert to that approach. Some form of automatic 
> memory management and a nice vector construction syntax should do the 
> job, and also happens to be really rather usefull for lots of other 
> stuff.
> 
> 
> //David Olofson - Programmer, Composer, Open Source Advocate

                Bob Pendleton
> 
> .- Audiality -----------------------------------------------.
> |  Free/Open Source audio engine for games and multimedia.  |
> | MIDI, modular synthesis, real time effects, scripting,... |
> `-----------------------------------> http://audiality.org -'
>    --- http://olofson.net --- http://www.reologica.se ---
> 
> 
-- 
+---------------------------------------+
+ Bob Pendleton: writer and programmer. +
+ email: Bob@xxxxxxxxxxxxx              +
+ web:   www.GameProgrammer.com         +
+---------------------------------------+


Other related posts: