Hi,
I thought that I will write down my comments on AS that emerged in my head recently instead of making a lot of threads, so here they are. If it makes any difference, my background in such languages comes from LPC language used on MUDs for countless years and modern scripting languages like Pyhon/Ruby. I know AS may never come even close to their flexibility, but there may be few things that could ease the use of it and maybe not require rewriting everything :)
Also some things may already be possible, but I don't know about it. I digged through documentation many times but I could miss some things anyway, so if something on the list is already possible (maybe in a bit different way, but still possible) - let me know.
I understand some ideas may sound bad to purists - just take into account that I don't know much about AS internals and I'm approaching it more from a user side of view. And I'm coming from a background where there was a thing called "function shadowing" where special object in the virtual world could shadow another, intercepting certain function calls to shadowed object, changing their behaviour. It allowed people to code things no one ever imagined (horse mounts, changing into vampire on runtime, illusions, spell effects and many more), but sounds like terrible programming practice :)
1. Enum & const int conflict
enum MyEnum {
Foo = 1,
Bar
}
const int Foo = 2;
print(formatInt(Foo)) => 2
So it looks that const int declaration silently masks the enum in the global scope. It still can be refered to as MyEnum::Foo, but
the problem is if we want to use enum in old-C style and it's possible to accidentally mask such enum with const int
anywhere in the code.
Maybe there should be two kinds of enums like typical C enum and "scoped enum" similar to enum class in modern C++, where
you can't refer to enum without prefixing it with the name (MyEnum::Foo). The "old way" would work both ways, so either
Foo or MyEnum::Foo.
If I swap the the order, const int will prevail but without any compile-time warning that we're declaring an enum with
the same name as existing const.
Summary: It would be good to give some solution for both kinds of uses, either as global-scope const int, or scoped
more type-safe "enum class". Otherwise for global-scoped const ints it's safer to declare several const ints because
they won't be possible to override, like:
const int Foo = 1;
const int Bar = 2;
etc.
2. Shared types
When I compile a type, for example class inheriting from shared type, I still have to #include files where these types are
declared in full. For bigger class hierarchies this may be a lot of code to parse that won't even be compiled (if I'm correct?)
because these types already exist. Wouldn't it be possible to do some kind of "forward declaration" which tells the compiler
that these classes are already compiled and shared? Something like:
using interface IWeapon;
using mixin MPoisoned;
using class Object;
using class Item;
class Weapon: IWeapon, MPoisoned, Object, Item
{
}
From what I understand in case of types like interface, class, mixin the name should be unique identifier that should be enough
to find the correct shared type. For functions this would probably require full declaration?
Or I'm wrong here and it's not really needed? Somehow it works for C++ registered types and we don't #include any code, so how
can I achieve the same for script types?
3. Wrapping/intercepting a function/method call
Would it be possible to wrap a call to a function with another function? Or some callback that could be registered and fired when
calling a function? This would be C++-side functionality but could be used on script side too (like Python decorators for example).
I see several use cases, from access control (who can and who can't execute function) to real-time masking or wrapping certain
functions with other functions.
4. Auto resolve of return type for functions / C++ registered methods
I see there are no "map/filter" functions in Angelscript. These are extremely useful and available in many languages. Lets say
I want a function that will take array argument and execute some anonymous function on every argument, then return such array back.
array<int> foo = { 1, 2, 3}
auto bar = map(foo, function(e) { return formatInt(e); }); => bar becomes array<string> bar = { "1", "2", "3" }
auto baz = filter(foo, function(e) { return e > 1; }); => baz becomes array<int> baz = { 2, 3 }
This means that map/filter function need to take enumerable type like array<T> and return the same array<T> type or different T (in case the type changed during map() call).
Such constructs would be extremely convenient for scripting. There is a problem with a strong typing, but the return argument from
map() example could be deducted from anonymous function return argument, the problem is that we can't really register a function
with unknown return type, and decide on it on runtime.
5. Implicit casts from "variant" types, like the one used in dictionary
Right now I can't just do:
dictionary dict = { { "strval", "foo" }, { "intval", 5 } }
string myval = dict["strval"];
int myval2 = dict["intval"];
because it will throw "Can't implicitly convert from 'dictionaryValue' to X" error. Why, knowing the type of stored value, couldn't
it actually do this somehow and return string or raise runtime error saying that the type of assignment doesn't match? This seems like
a minor thing, but would be very convenient if it's doable.
6. Access mixin through a namespace
I wrote about it some time ago when mixins first appeared. It would be nice if we could refer to mixins through their classname, like:
mixin MFood
{
void init(); // initializes internal state for food functionality
bool eat();
}
mixin MItem
{
void init(); // initializes internal state for item functionality
bool take();
}
class Burger: MFood, MItem
{
Burger()
{
MFood::init();
MItem::init();
}
void take()
{
if (!paid_for)
{
print("You can't take it yet! Pay first.");
return false;
}
return MItem::take();
}
// bool eat() stays exactly the same as in mixin, Burger doesn't try to define it's own version
}
It would _greatly_ increase the usefullness of mixins, at least in my case. Right now we can't have some initialize function for mixin,
and have to rename them to init_food() and init_item() - this is not a big deal. The bigger deal is that we can't refer to mixin code once
we override some function. There is a workaround by having 2 methods in mixin, one that has the code to call, and the other that calls the
first function. Then if we override the main function in class inheriting from a mixin, we still have access to that code through the other
method. But using it a lot is troublesome as we're making additional methods just to be able to refer to original mixin method. This could
happen automatically if the inheriting class overrides method of a mixin - if it does, the original mixin method is preserved as a method
with special namespace (Mixin::method for example but it could be even MixinName_method if that's easier to achieve). This way in a case
of method override we'll have access to mixin, but if there is no override - only mixin method remains (though maybe for a sake of coherency it
should be available always through the Mixin:: prefix?)
7. &inout for primitive types (int, float?)
Is there really no way to make this work? Liks in this code:
void apply_modifier(int mod, int& value)
{
value += mod;
}
int value = 5;
for(int i = 0; i < 10; i++)
{
apply_modifier(i, value);
}