1. Hello Guest! It's with a heavy heart that we must announce the removal of the Legacy GMC Archive. If you wish to save anything from it, now's the time! Please see this topic for more information.
    Dismiss Notice

Discussion "method call" syntax sugar

Discussion in 'GameMaker Studio 2 Community Tech Support' started by YellowAfterlife, Nov 15, 2016.

Tags:
  1. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,436
    I think this is about the right time to bring this up.

    While it'll probably be some time until (if ever) GML would feature much loved and hated "object-oriented" features, there is a thing that would ease some situations while likely having low implementation cost.

    What I propose is a bit of syntactic sugar "method calls".
    In short, it would mean that doing
    Code:
    inst.method(1, "hi");
    would be roughly equivalent to
    Code:
    with (inst) script_execute(method, 1, "hi");
    On the technical side, it would mean augmenting compiler a little to recognize attempts to call field access (`inst.field`) expressions, and parse them accordingly:
    if field name matches a built-in function name,
    if the function makes use of caller instance (instance_destroy versus show_message),
    convert to a function call upon the instance.​
    else show error (for the lack of sense in the act).​
    else if field name matches a script name,
    convert to a script call upon the instance.​
    else convert to value retrieval and script call of it upon the instance.​

    Since GameMaker does not allow calling scripts stored in arbitrary expressions via `expr()`, there is no ambiguity in syntax.

    As far as runtime side of things goes, things look like they should be easily doable too - both JavaScript and YYC pass "self"\"other" pointers as function arguments, meaning that a context swap for a script call is easy and fast enough to do. It is harder to tell about VM (push context - get value - call - pop context?), but a separate "get & call" bytecode instruction could be introduced in worst case.

    As proof of concept, I've implemented the feature in GMLive this afternoon -- live example. Code:
    Code:
    var q = instance_create(0, 0, obj_blank);
    q.name = "GMLive";
    q.greet = scr_greet;
    
    q.scr_greet(); // `GMLive says "hi"`
    q.greet(); // `GMLive says "hi"`
    q.instance_destroy();
    
    #define scr_greet
    show_debug_message(name + ' says "hi"');
     
    rytan451, Rukola, Oranjoose and 8 others like this.
  2. csanyk

    csanyk Member

    Joined:
    Jun 20, 2016
    Posts:
    821
    I love the idea.
     
  3. Hyomoto

    Hyomoto Member

    Joined:
    Jul 7, 2016
    Posts:
    1,078
    I think it works a QOL improvement, and I'm hardly against it. The syntax comes off a bit cleaner, but like I said in the other thread I'm guessing it's not high priority because GML has ways of accomplishing this.

    I mean, instead of:
    Code:
    a.instance_destroy()
    You simply do:
    Code:
    instance_destroy( a )
    At best, your example saves a single argument in the script, but you have to type it either way in the calling syntax. I'm not really debating whether or not it should exist, but I'm guessing that's why it's not a high priority.
     
  4. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,436
    While a general function case is workaroundable by always having "this" as first argument, the highlight of the post is not the built-in functions, but calling scripts stored as variables in instances as methods (second case in the sample) - if you have
    Code:
    inst.field = scr_some;
    there is no short way of calling it out of it without code duplication,
    Code:
    script_execute(inst.field, inst, ...args)
    intermediate variables,
    Code:
    var v = inst; script_execute(v.field, v, ...args)
    or variable_ functions and other weird things:
    Code:
    fieldcall(inst, "field", ...args);
    #define fieldcall
    /// fieldcall(ctx, field, ...args)
    var q = argument[0];
    var f = variable_instance_get(q, argument[1]);
    switch (argument_count) {
        case 2: with (q) return script_execute(f); break;
        case 3: with (q) return script_execute(f, argument[2]); break;
        case 4: with (q) return script_execute(f, argument[2], argument[3]); break;
        // and so on
    }
    
    (which are basically Reflection and won't be too fast)

    Some other approaches exist, but nothing comes off as particularly laconic.
     
  5. Hyomoto

    Hyomoto Member

    Joined:
    Jul 7, 2016
    Posts:
    1,078
    I think you may be overthinking this quite a bit. I absolutely agree with you, what you are trying to accomplish is a nightmare under current circumstances. But ... may I ask why? As in, why code this way instead any more literal ways? I ask because I used to think this sort of stuff would help me out. If I could assign a script for execution here then I could do blah blah blah. But now I do it in a way I consider more appropriate to GML which is instead to keep private functions private, and if I need two things that do the same stuff but otherwise have additional behaviors they would either be separate objects with the same parents or in a bizarre case, if I need a script to behave differently depending on who called it, I'd just check in the script. I mean, your method looks like it has major potential for inducing errors into the program. I've messed around with Java and Ruby and it's interesting how you can overwrite a class, then link back to the original class (sorry if my terminology is off there), but overall it comes off as a nightmare in terms of knowing what should be happening when and where. Personally I'm of the mind that stuff should all be under strict control at all times. It's why I don't use a lot of the built-in functions in GML, just letting the application take care of it seems like a recipe for unexpected outcomes.

    So, why do script_execute when you can just do script in the first place? At what point are you adding complexity for the sake of complexity? I'll be the first to admit I'm not a computer engineer, and I do not have a degree in computer sciences so maybe I'm missing the 'duh' right in the middle, but then again if that stuff has to be true, who does this behavior really benefit?
     
  6. Salvakiya

    Salvakiya Member

    Joined:
    Jul 17, 2016
    Posts:
    84
    if they were to add method calls like that we should also get member methods... Oh please let us have more OOP!
     
    ZeDuval likes this.
  7. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,436
    For the exact same reason there is object event inheritance - because you want object sub-types to do their own action without explicitly checking for what type it is whenever that is to be done.

    If you say that inheritance is evil, do you make games without any? Probably (hopefully!) not.

    You can, of course, have a script that just houses a giant switch() block to pick actions to do based on object_index, but in doing so you accomplish the exact same thing, only with more code. The code did not magically become more or less safe from splitting off decision making into a script instead of having it defined in instances. It might be a tiny bit better than "user events", however, since those end up being reused for different purposes on different objects, and therefore must be used with care (or you'll call something you didn't intend to call)

    Further reading: virtual functions.

    The most desirable solution would be to be able to define custom events/functions right inside the object interface, but that is more work, obviously enough.
     
    Oranjoose likes this.
  8. Hyomoto

    Hyomoto Member

    Joined:
    Jul 7, 2016
    Posts:
    1,078
    I understand the use of virtual functions, or at least the basic idea of how they work from Ruby. I find it fascinating that you can override and inherit classes, but I submit to you that I can already accomplish a variation of this using GML using event_inherit. I used to always use scripts for everything, I never typed a line of code into an object until recently when I started doing a lot of user interface work. I realized that there is no amount of pre-work I can do that can overcome the individual circumstances of an expected outcome. So, I use functions as f(x) literals. Not for game logic, but for calculations that can be generally useful in many situations but are a chore to type repeatedly, or literally functions rather than scripts:
    Code:
    if argument0 < argument2 || argument0 > argument2 + argument4 || argument1 < argument3 || argument1 > argument3 + argument5 { return false }
    return true;
    
    If I need to borrow code, I simply build another object level almost identically to what you are doing in OO, with the sole exception that I cannot modify it at run time. Without using strange scripting conventions, it's not possible for me to overwrite the inherent behavior of my objects of course ... or is there? I can use instance_change. And in that case instead of usingSo rather than using script_execute, I could instead change my instance into one that performs the actions I want. When I ask why? I'm asking you, what is it that you can't accomplish in GML that the use of these structures is the only way. Instead of trying to turn GML into something it currently isn't, at what point do you try to use GML for what it is?

    Though, to be fair, I'm still on board with:
    Code:
    a.execute()
    Since it's a nice improvement to syntax. I just don't see the point in overhauling the language to add complexity that doesn't seem to yield equal returns. Once again, I say this not as a computer science major but as the general user. How useful is this change to the 80%, or even 50% of average users? And what percentage does it serve to simply add complexity and alienate?
     
    Last edited: Nov 17, 2016
  9. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,436
    The point is not changing something at runtime (which you already can, but ideally should not do), the point is being able to write
    Code:
    other.take_damage(5);
    in a bullet<->enemy collision event, and have take_damage set to a "usual" (life -= argument0) script in base enemy, have it set to a script with damage reduction in some enemies, have some enemies retaliate upon taking damage, and so on - the result is equivalent of event system (excluding event_inherited calls) for custom actions. You can't easily do that with events and can't do that with with+script_execute without writing additional code.

    Because games sometimes have complex parts (networking, editors, API integrations, ...) and you can't just go "welp, that's it, time to rewrite this game in something more suitable for these parts". Moving code to native extensions often isn't an option either, since extension code is very limited in how it can interact with game code.

    It's not overhauling a language nor adding complexity. It's literally adding a shorter way of doing something that you already can (albeit with more code or hacks).
    Same as with ternary operators. Same as with array declarations. Same as with macros. Same as with enums. Some people will sing praises for them (since they do make their work easier), others will say that these are not needed.

    Making assumptions on how many people would use something based on whether you see use for it by yourself is ignorant at best. I would love an explanation of how seeing a "instance.function()" in code is going to yield sense of confusion and existential terror in someone - there are very few things that the syntax can mean (and it means exactly this in other programming languages), and it is up to user whether to do anything with it or not.
     
  10. Hyomoto

    Hyomoto Member

    Joined:
    Jul 7, 2016
    Posts:
    1,078
    I have said, twice, that I agree with the syntax, adding only that I doubt it's high priority because there are already methods to achieve the same results. What I don't agree with is this example:
    Code:
    fieldcall(inst, "field", ...args);
    #define fieldcall
    /// fieldcall(ctx, field, ...args)
    var q = argument[0];
    var f = variable_instance_get(q, argument[1]);
    switch (argument_count) {
       case 2: with (q) return script_execute(f); break;
        case 3: with (q) return script_execute(f, argument[2]); break;
        case 4: with (q) return script_execute(f, argument[2], argument[3]); break;
        // and so on
    }
    I'm saying rather than that example, that you simply code in a way that it becomes irrelevant, to which you've apparently somehow compared to macros and enums. And to be fair, I could make the same case for enums and macros. If the point of your syntax change is not a general case scenario, the calling of built-in functions and user-created scripts, but is instead "calling scripts stored as variables in instances as methods", then the difference between macros and enums is that the code you've demonstrated is not a common problem, I doubt you would make use of it, and that being the case, what value does your proposed solution have when backed by an edge case you yourself have claimed is a workaround? Hence, I asked the question, how valuable do you think this is to the majority of users? Not because I speak for all of them, but because is it at all possible that you are seeking a complex solution to a simple problem you already have the tools to solve but choose not to because you really like OO structures? Is it at all possible that the case scenario you've described is uncommon, or that there may be already existing and better solutions for it? And do I even need to point out I mean in GML. We're not discussing other languages, so the need for these structures elsewhere is not support for their inclusion in GML. If that's the case you might as well ask Microsoft to include them in QBASIC.
     
  11. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,436
    I wouldn't expect this to be a high-priority issue, only to be implemented sometime, in some form.
    For very strong opinions on OO, you can see the much longer discussion in topic about OO.
    In general case, OO[-ish] implementations could be rated as following (from more proper to less proper):
    1. Actual classes with compile-time checking.
      Highest performance, but GML is a dynamically typed language so this probably won't happen.
    2. Methods/custom events defined in object editor.
      As I had mentioned, this is a more proper solution, but implies substantially more effort to implement.
    3. "prototype" structures (see JS, Lua).
      Most people don't really like them, since they are confusing enough.
    4. "syntactic sugar" (":" operator in Lua, "." operator in many scripting languages)
      Merely a workaround, but a language-level one, and still convenient from user perspective.
    5. Hacks and workarounds
      As demonstrated - you get to choose between writing additional code per use, writing additional scripts, or code that makes you question the entire situation (quoted). None of these are desirable.
    Obviously it's up to YYG to decide what (if anything) they intend to implement.

    As per your doubt as to whether I would use it, the last project that I've finished is a feature-complete GML interpreter running in GML (a close relative of the live example program in original post). It's written in Haxe and compiled to GML, since the lack of specific features results in a situation where the most optimal code is also the one that you wouldn't want to write by hand no matter what. Explaining how it comes to that is basically worthy of a full-sized blog post though.

    Overall, I don't think there's anything else to discuss on here.
     
    Last edited: Nov 17, 2016
    Oranjoose likes this.
  12. Salvakiya

    Salvakiya Member

    Joined:
    Jul 17, 2016
    Posts:
    84
    dont think this method has been talked about yet but.... BA BAM! https://forum.yoyogames.com/index.php?threads/methods-member-script-functionality-improvement.13091/

    thoughts? its a hacky way to have "member scripts". Not even sure if that's what they should be called. but even if you dont use the arguments it lets you rename the user_events by only changing a single line of code... 2 if you include the /// comment at the top. This also allows the "member scripts"/"methods" to be inherited.

    also... haxe to GML??? where is this thing you speak of? what did the code look like before it was GML?
     
  13. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,436
    Haxe code looked like this originally:
    upload_2016-11-20_16-17-17.png
    As can be seen, it's a bit more organized. Cyan-colored function calls are macros. Most other functions are inlined when conditions permit.
    Haxe->GML compiler is something that I've been working on the past 2-something years. It currently is not publicly available. Not sure if/when it will be, since there's still a lot missing (basically only supports a language subset that I need in my work) and doing ongoing technical support for this would be very time-costly.

    As per suggested idea, I've done similar before (with user events only though), and wouldn't say that it's exceptionally desirable...
    Yesterday, looking through my mockups of how Haxe' function closures could possibly be compiled to GML, I noticed that there's a slightly less hacky workaround.

    The idea's as following: when you define a method, you create a mini-structure ("closure") holding a reference to the script and an instance id that it should be called for.
    Code:
    /// mt_wrap(script, context = self)
    return [argument_count > 1 ? argument[1] : id, argument[0]];
    and you also have a separate small script for calling these, which would extract script+instance from that mini-structure and call the script on the instance:
    Code:
    /// mt_call(mt, ...args)
    var q = argument[0];
    with (q[0]) switch (argument_count) {
        case  1: return script_execute(q[1]);
        case  2: return script_execute(q[1], argument[1]);
        case  3: return script_execute(q[1], argument[1], argument[2]);
        case  4: return script_execute(q[1], argument[1], argument[2], argument[3]);
        // [continue till maximum desired argument count]
        default: show_error("Too many arguments in mt_call.", false); return 0;
    }
    show_error("Could not find instance " + string(q[0]) + " to call " + script_get_name(q[1]) + " on.", false);
    Therefore, you can do
    Code:
    var q = instance_create(0, 0, obj_blank);
    with (q) {
        name = "GMLive";
        greet = mt_wrap(scr_greet);
    }
    mt_call(q.greet); // 'GMLive says "hi"'
    
    #define scr_greet
    show_debug_message(name + ' says "hi"');
    GMLive demo.

    Obviously enough, with this you can't call scripts or functions, and don't get automatic inheritance (might need to think more about this) but otherwise it matches my requirements fairly well.
     
  14. Rukola

    Rukola Member

    Joined:
    Jun 20, 2016
    Posts:
    72
    Seems only natural to have this function :)
     
    Last edited: Nov 21, 2016
  15. GMWolf

    GMWolf aka fel666

    Joined:
    Jun 21, 2016
    Posts:
    3,470
    I want too see this. Just the syntactic sugar is fine.
    out with the 'with', in with the '.'.

    GML will need a slight change as to how it handles the . operator, so that foo.bar() will work as intended when bar is a variable pointing to a script.
    But it would be great.
     
  16. Oranjoose

    Oranjoose Guest

    Throw around "high/low-priority" all you like, but this feature is make or break for me. I teach game programming, and the existing convoluted workarounds for creating and calling custom functions is entirely a deal-breaker, pedagogically that is.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice