Changes to %fn%(), fn.call() or func('fn') syntax?

Discuss the future of the AutoHotkey language
sirksel
Posts: 43
Joined: 12 Nov 2013, 23:48

Changes to %fn%(), fn.call() or func('fn') syntax?

20 May 2019, 17:02

Many thanks to @lexikos for all the work he has done to enable fat arrow notation (e.g., fn(x,y) => x*y and x => x*2) and method/property shorthand (e.g., property[] => expr()). It takes Func.Bind() to a whole new level, and really enables much more efficient functional programming (e.g., map, filter, compose, pipe, transduce, etc.). I don't think fat arrow in AHK allows blocks, but multi-statement makes most functional constructs possible anyhow. That said, a couple small sticking points remain that make functional programming in AHK a little less streamlined/clear than it could be:

1. Function objects can only be called as fn.call() or %fn%() rather than fn(). Unlike a lot of other languages that have first-class functions, AHK seems to require that every function composition, partial, etc. (anything returned as a function/boundfunc/closure object) must be called in this more roundabout way.

2. Explicitly defined and built-in functions can't be referenced/passed without a Func() wrapper creating the function object. It makes for slightly awkward pipe/compose constructs when mixing defined functions with other intermediate (and possibly non-global) function objects, especially when trying to compose in a pointfree style. For example: strbaz := compose(func('strsplit'), strbar, func('strupper'), strfoo). I've also tried doing this: strbaz := compose('strsplit', strbar, 'strupper', strfoo), then checking param type and auto-wrapping. It reads slightly better, especially when the composition is nested or more complex, but it's a bad option because it's more error-prone and bypasses the interpreter optimizations.

Any thoughts? Are these design decisions that you plan to preserve in v2? Are they possibly part of your consideration for changes, along the lines of the other %% discussions in the Objects preview thread?
lexikos
Posts: 6397
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Changes to %fn%(), fn.call() or func('fn') syntax?

22 May 2019, 04:31

I'm sure this has come up multiple times, but I wasn't able to find a good topic to reference.

1. %% or .Call is required when calling a value (typically contained by a variable), regardless of the type. What you want is for xxx() to permit both function names and variable names without any differentiation. This requires combining the two into one namespace, which implies that you can no longer have a function and a variable with the same name in the same scope. I believe these are the main drawbacks:
  • Assignments accidentally overwriting functions. This can be mitigated by making function identifiers read-only (I mention it because it's a common issue in some languages, and would be even more so here due to case insensitivity).
  • Variables accidentally shadowing functions by the same name. If the variable does not contain a function, this would cause a runtime error when the function call is attempted. If the variable does contain a function but not the one the author intended to call, the error is harder to detect.
  • The reduction in load-time error checking. For example, if an author makes a typo in the function name, it might be difficult to catch the error at load time since the author might have been intending to call a variable. (Aside from the above problems, load-time validation of function parameters can still take place, provided that it is not possible to overwrite a function at runtime.)
2. Generally Func returns the function itself; the same object that a static call such as MsgBox() references. Even if you were able to reference the function directly by name, what you would get is exactly the same. There is no wrapping or creating, except in the case where the function contains upvars, where Func creates a Closure referencing the original function and captured variables. (The current implementation permits a nested function to be called directly without the overhead of creating a closure.)


I had written the following in v2-thoughts, but hadn't updated it online (for no particular reason):


Detecting mispelled function names at load time is a useful feature of the language, but currently only applies to direct calls. References such as Func("name") or "name", if mispelled, will only be detected when the script attempts to call them (which might not be where they are referenced).

Func("name") is optimized (in v2) to resolve at load time, but cannot raise a load time error when the function does not exist because it is not an error. For instance, name can be defined in an optional #include *i, and Func is how it would be resolved to be called. If it raised a load time error, this would not work:

Code: Select all

    if f := Func("name")  ; ← load time error
        f.Call()
Nor would this:

Code: Select all

    if IsFunc("name")
        MyRegisterEvent(Func("name"))  ; ← load time error
The most obvious solution is to add new syntax for a function reference which would be caught as an error at load time if the function does not exist.
  • fincs suggested @name, as in OnExit(@ExitHandler) or greet := @MsgBox.Bind("Hello").
  • Another idea is Func'name', as in OnExit(Func'ExitHandler') or greet := Func"MsgBox".Bind("Hello"). Although not currently valid, it might be difficult to visually distinguish from auto-concat.

Return to “AutoHotkey v2 Development”

Who is online

Users browsing this forum: No registered users and 8 guests