YoYoGames / GameMaker-Bugs

Public tracking for GameMaker bugs
13 stars 5 forks source link

constructor_call and is_constructor #3055

Open Alphish opened 10 months ago

Alphish commented 10 months ago

Is your feature request related to a problem?

Sometimes it would be useful to dynamically create a new struct from a constructor using an array of parameters, just like you can do with method_call or script_execute_ext. Additionally, one might want to tell a constructor function apart from non-constructor ones, to make the correct type of call (new some_func() for constructor vs some_func() for non-constructors).

Describe the solution you'd like

I would like new functions: constructor_call(ctor,args) and is_constructor(func) to handle the scenarios described above.

E.g. constructor_call(Foo, ["bar, "baz"]) would be equivalent to new Foo("bar", "baz"). This would contrast with script_execute_ext(Foo, ["bar", "baz"]) which would be equivalent to Foo("bar", "baz") instead.

Example code using these:

function create_generator_callback(_func, _args) {
    return method({ _func, _args }, function() {
        if (is_constructor(_func))
            return constructor_call(_func, _args);
        else if (is_method(_func)
            return method_call(_func, _args);
        else
            return script_execute_ext(_func, _args);
    });
}

Describe alternatives you've considered

No response

Additional context

No response

tabularelf commented 10 months ago

Personally, is_constructor is one of those big ones for me! constructor_call is also something useful in some scenarios... Having a parser being able to work out between a constructor and a function/method, and then passing in an array of arguments depending on the status is a dream come true! Currently I have to go by jankier solutions, and I personally rather kill off any unnecessary switch statements for something like this. ^^

AtlaStar commented 10 months ago

I think that this should be the main mechanism to fulfill request YoYoGames/GameMaker-Bugs#3057 personally, with the constructor call method taking an optional initializer struct similar to the instance_create_* methods. Therefore I would also suggest a constructor_call_ext function with the signature of constructor_call_ext(constructor_fn, initializer_struct, [args]).

It also may be cool if the args field wasn't necessary but something included in the initializer struct...i.e using the examples provided, instead of calling constructor_call(Foo, ["bar, "baz"]) or even the proposed constructor_call_ext calling constructor_call(Foo, {argument0: "bar", argument1:"baz", struct_prop1: "something"}) where argument0 and argument1 bind to the proper locals automagically.

yerumaku commented 1 month ago

@AtlaStar

I think that this should be the main mechanism to fulfill request #3057 personally, with the constructor call method taking an optional initializer struct similar to the instance_create_* methods. Therefore I would also suggest a constructor_call_ext function with the signature of constructor_call_ext(constructor_fn, initializer_struct, [args]).

It also may be cool if the args field wasn't necessary but something included in the initializer struct...i.e using the examples provided, instead of calling constructor_call(Foo, ["bar, "baz"]) or even the proposed constructor_call_ext calling constructor_call(Foo, {argument0: "bar", argument1:"baz", struct_prop1: "something"}) where argument0 and argument1 bind to the proper locals automagically.

What behavior do you expect when you pass a structure initializer_struct with static and non-static methods?

Why make two functions when you can add optional arguments to one, and you can transfer several structures at once?

constructor_call(constructor_fn, [args], [initializer_struct_1st], [initializer_struct_2nd], ...)
Hyomoto commented 2 weeks ago

I imagine there was more internal discussion over how to approach this, but I think it's still very much worth considering. One of the unusual bits about the constructors in GM is that they function as both functions and constructors depending on whether or not 'new' is used. I have attempted to find places where this quirk is useful but seems mostly to be an odd "gotcha" case in the language.

I think it's arguable a new function shouldn't be needed at all as one could reasonably expect calling a constructor to fulfill it's intended purpose and this would just allow using method_call. I'm don't know if this is too mired in the language the be corrected now, but that gives two vectors to addressing the issue: either have constructors be an identity of the function that affects its result, or "functional parity" between functions and constructors by providing independent functions.

For the sake of discussion I think the former is better in terms of upkeep and accessibility. There may be some condition whereby the context of the constructor is useful, but generally one would expect a constructor to behave as a constructor, otherwise the constructor keyword is superfluous since the new keyword should address the utility of the call. I know the approach is to not break existing functionality as often as possible, but it seems at least worth considering whether or not this quirk needs to be preserved. Much as the delete keyword is pretty much cosmetic, new could be depreciated by patching the hole and letting constructor properly inform the intent of calling the function thus avoiding adding more functions for constructors/methods/script functions with varying scope as has generally been the case.

Either way the function is desirable even if only to achieve functional parity.