[AGS4 Feature Proposal] Variadic functions

Started by Snarky, Fri 02/12/2022 09:17:32

Previous topic - Next topic

Snarky

See thread.

AGS Script allows you to call functions like String.Format(), Character.Say() and Display() with a variable list of arguments of "arbitrary" types. However, it is not possible to create custom variadic functions in AGS Script (or, at least, there is no way to actually read the argument values). Some way to support this would make the scripting language a lot more powerful and convenient: for example, you could write mathematical functions that could take either ints or floats as arguments (function overloading), create generic functions that could, for example, manipulate arrays of arbitrary types, etc.

Of course, it may be that there are better approaches to achieve those goals, but I thought since it already exists in the engine somehow, this might be relatively easy to implement.

Crimson Wizard

#1
Quote from: Snarky on Fri 02/12/2022 09:17:32Some way to support this would make the scripting language a lot more powerful and convenient: for example, you could write mathematical functions that could take either ints or floats as arguments (function overloading), create generic functions that could, for example, manipulate arrays of arbitrary types, etc.

Variadic functions, function overloading and generic functions are completely separate language features, that should not be mixed!!!

Variadic functions do not restrict the number of args, and are not typesafe (you cannot enforce an argument type in the prototype). Overloaded and generic functions restrict number of args and are typesafe (with generic function you can at least enforce that arguments and return values are of the same type, if necessary).

Snarky

They all enable you to create functions that can take different argument types, though.

Crimson Wizard

Quote from: Snarky on Fri 02/12/2022 10:04:57They all enable you to create functions that can take different argument types, though.

My meaning is, that you should not implement function overloading and generic functions with variadic function. That's not safe and probably won't work correctly. They are meant for separate kinds of situations too. They have to be 3 separate language features.

Crimson Wizard

#4
Trying to make a very quick overview of the problem.

1. Variadic function:
Code: ags
function VarFunc(<optional strict args>, ...)
Number and type of arguments cannot be known at the time of compilation, so user may pass virtually anything, in any order. This makes this an uneasy function to write.
Will require a way to iterate through the arguments (access them by index like array, or using "get next arg" syntax).
It's not clear whether getting argument's type, or rather - argument as certain type - any type, - is a feasible thing in AGS script, as in AGS variables must have strict types, they are not variants (like in Lua or Python etc). It's quite possible that this would require to have a function that retrieves an arg casted to the wanted type, which in turn requires a generic function (so - generic functions support).

If above is implemented, somehow, the common use of the variadic function will likely be operations that work on arrays of random types of data. Like logging, for example. Using variadic functions for operations that require strict types and number of args will require args checking and failing if types and number of args don't match certain requirements. Also, in the latter case, calling such function will be very bug prone (and may be difficult to explain to users).

One thing that variadic script function may be definitely useful for - is for forwarding variadic arg list further into the engine API, without knowing its actual contents.
For a hypothetical example:
Code: ags
function CustomSay(<some args>, ...)
{
     // do some stuff
     Display(text, __var_args__); // forward unknown arg list further into the engine
}

2. Function overloading.
Code: ags
function DoSomething(int i);
function DoSomething(float f);
function DoSomething(String s);
This means having multiple separate functions of same name but with different arguments. Besides that these are just regular functions, each having its own body. This is useful to have a set of functions that do similar thing, but may do so this way or another.

Personally, I think this may be easiest to implement in the new compiler, because this may require only extending how the function registration works internally: for example, besides just the function name (and number of args) also have argument list description in the internal function name. This way it will be possible to distinguish them in both compiler and the engine.

3. Generic functions.
Code: ags
T Sum<T>(T a, T b)
{
    return a + b;
}
The point of the generic function is to have a single operation that can be performed over a multitude of different types. The argument list (and return value) enables types that match certain requirements. Like, in above example: can be summed together. If user tries to call this function with types that cannot be summed, then the compilation should fail.

Generic functions in script are best for the simple algorithms that don't depend on types too strictly. Like: may be performed over any kind of number, or iterating over any kind of array, and so forth.

Personally, I have no idea how possible or difficult that may be to implement this in AGS script.

Snarky

#5
I have no idea how difficult the different options are to implement. For variadic functions, I imagined that the API might look something like this in use:

Code: ags
function VarFunc(int foo, String bar, ... args)
{
  if(args.Count > 0)
  switch(args.Type[0])
  {
    case eTypeInt:
      int a = args.ValueAsInt[0];
      // Do something with the argument
      break;
  }
}

In other words, the variadic arguments would be exposed to the function as a (managed?) struct with array attributes for the type info (as an enum) and to provide access to each argument by its appropriate type (really just a getter).

This would allow you to do a lot of things that can be done by function overloading or generic functions (though not different return types in one function, and probably not support for custom types as arguments), at some slight inconvenience. Of course, you couldn't guarantee at compile time that each function call has the right number and types of arguments, but that's not necessarily a huge problem. After all, String.Format() cannot confirm at compile time whether the variadic arguments match the %tags in the string either.

On the other hand, if it turns out to be easier to implement the other options, they would, together, also cover most of the interesting use cases. And with the ability to pass them on to the native variadic functions added to that, I can't really think of any realistic use case where I'd miss the remaining variadic functionality.

Crimson Wizard

#6
Quote from: Snarky on Fri 02/12/2022 14:27:11This would allow you to do a lot of things that can be done by function overloading or generic functions (though not different return types in one function, and probably not support for custom types as arguments), at some slight inconvenience. Of course, you couldn't guarantee at compile time that each function call has the right number and types of arguments, but that's not necessarily a huge problem.

I'm so strongly disagreeing with this paragraph, that apparently we have a difference in views on programming at the fundamental level.

Snarky

Quote from: Crimson Wizard on Fri 02/12/2022 14:49:59I'm so strongly disagreeing with this paragraph, that apparently we have a difference in views on programming at the fundamental level.

My view is that if you have a need, it's better to have some tool to solve it than no tool at all, even if the tool is not perfect. No, variadic function calls, including calls to AGS functions like Display, Character.Say and String.Format, are not "safe" (but then again, no programming language can prevent users from writing code that breaks) because they cannot be fully checked at compile time, but if it was the only way to write functions that could accept different argument types, that would still be very useful.

Crimson Wizard

#8
Quote from: Snarky on Fri 02/12/2022 15:30:32No, variadic function calls, including calls to AGS functions like Display, Character.Say and String.Format, are not "safe" (but then again, no programming language can prevent users from writing code that breaks) because they cannot be fully checked at compile time, but if it was the only way to write functions that could accept different argument types, that would still be very useful.

For these kinds of functions being variadic is the typical way to achieve what they need. The other way would be to accept an array of "variant" type. In any case they must have a list of "variant" kind of args, that is a list of arguments where neither number, nor order, nor types of arguments is predefined.

Naturally, if this is what your script function is intended for, then it also has to be variadic.

But common overloaded functions and generic functions do not require "variant" args, they require a strict argument list where the number, order and types are predefined (types are not predefined in the case of generic functions, but rather a context which determines whether particular types will be suitable).

In my opinion, using variadic functions as a implementation of function overloading and generic functions will shift AGS towards completely different kind of scripting, with less type safety, obscure function prototypes, and unreliable function behavior.

SMF spam blocked by CleanTalk