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 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.) [...] > > 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. 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. 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. :-) > > 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... Function and operator arguments can only be passed by reference or by value. 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. Anyway, no big deal. I just though it was a good idea to make "=" just another operator, but it seems like I was wrong. > > 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 .- 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 ---