Re: Changes to %fn%(), fn.call() or func('fn') syntax?
Posted: 04 Oct 2019, 10:33
Do you have a quick example? The ones currently on the help page are broken due to the Object changes, but something simpler would be nice.
Let's help each other out
https://www.autohotkey.com/boards/
https://www.autohotkey.com/boards/viewtopic.php?f=37&t=64712
Code: Select all
class f {
static call() {
msgbox a_thisfunc
}
}
%f%()
Code: Select all
namespace.s()
class namespace {
class s extends functor {
call(terms*) {
MsgBox % A_ThisFunc
}
}
}
class functor {
__Call(self, terms*) {
if (self == "")
return this.call(terms*)
if IsObject(self)
return this.call(self, terms*)
}
}
Code: Select all
namespace.s()
class namespace {
class s {
static call() {
MsgBox A_ThisFunc
}
}
static __Call(name, terms) {
return %(this.%name%)%(terms*)
}
}
Code: Select all
oFunc := Func("StrLen")
FuncSet("Func1", oFunc)
MsgBox(Func1("abc")) ;Error: Call to nonexistent function.
Code: Select all
;q:: ;use %func% syntax for all functions, even 'proper' ones
Func1 := Func("Func1")
Func2 := Func("StrLen")
;MsgBox, % Func1("abc") ;not this
MsgBox(%Func1%("abc"))
MsgBox(%Func2%("abc"))
return
Func1(vText)
{
return StrLen(vText)
}
;==================================================
;w:: ;pre-define all 'proper' functions, change their behaviour later
oFuncs := Map()
oFuncs["Func3"] := Func("StrLen")
oFuncs["Func4"] := Func("StrLen")
MsgBox(Func3("abc"))
MsgBox(Func4("abc"))
return
Func3(oParams*)
{
local
global oFuncs
return oFuncs[A_ThisFunc].Call(oParams*)
}
Func4(oParams*)
{
local
global oFuncs
return oFuncs[A_ThisFunc].Call(oParams*)
}
;==================================================
That's true. But I can settle for a bit less. Either:
Code: Select all
isdiv(y,x) => mod(x,y) == 0 ;is divisible by
neg(f) => x => !%f%(x) ;negate function factory WISH: this didn't need %%
iseven := func("isdiv").bind(2) ;is even WISH: this didn't need func("") here
isodd := neg(iseven)
;method of calling depends on how the function was created
;fn(x) vs %fn%(x) is my most common syntax error when writing in FP style
msgbox(isdiv(3,6))
msgbox(%iseven%(6)) ;WISH: this were just: iseven(6)
msgbox(%isodd%(6))
One doesn't have to be intentionally using function reference, derived functions or other functional programming concepts to be adversely affected by these language decisions.
These are among the options that I have considered. As with properties vs. array elements, my main concern is that this kind of prioritized mixing would be more a cause of confusion than a benefit. For objects, I chose not to mix properties and array elements at all by default, except with RegExMatch.Maybe the following might be an implementation alternative that keeps the namespaces separate, but instead introduces a couple of new syntax inferences:
Code: Select all
isdiv(y,x) => mod(x,y) == 0 ; normal function
neg(callable f) => x => !f(x) ; callable parameter
callable iseven := Func("isdiv").bind(2) ; callable variable
callable isodd := neg(iseven)
msgbox(isdiv(3,6)) ; normal function
msgbox(iseven(6)) ; callable variable
msgbox(isodd(6)) ; callable variable
; msgbox(isseven(7)) ; invalid
isodd := "" ; not necessarily an error
Code: Select all
;this assigns a function reference to a variable:
oFunc := Func("StrLen")
;to be used as:
vLen := %oFunc%("abc") ;3
vLen := oFunc.Call("abc") ;3
;this would assign a function reference to the function namespace:
FuncSet("MyFunc", oFunc) ;this
;FuncSet("MyFunc", "StrLen") ;or this
;to be used as:
vLen := MyFunc("abc") ;3
...it would be much better (in my opinion) if Func("iseven") returned iseven rather than failing. That way you don't have to know/remember whether the function is explicit or derived to be sure you're getting a function reference in a way that won't fail. Then you can then write general-purpose function factories, for example, that work on either explicit or derived functions.The declaration would have a similar effect to iseven(a*) => %iseven%(a*), except that Func("iseven") might fail or return the value of iseven.
I will think more and let you know if I come up with any bright ideas... although I somehow think your knowledge/skill at language design is hard to beat. I'll still try though. Thanks again for taking time to respond and for all you do to make AHK such a great language.1. This doesn't work both ways on its own, unless you create every function this way (which might require defining them in the auto-execute section, and could be difficult to implement for full function definitions).
This is a problem. Load-time optimization would pose a problem for your idea, as function names are resolved to addresses.jeeswg wrote:(That is if we permit non-existent functions at loadtime.)
Code: Select all
;MyFunc1 is resolved to an address (as per usual), it cannot be changed during runtime
;MyFunc2/MyFunc3 can be defined/changed during runtime using FuncSet (or equivalent)
MyFunc1()
oFunc := Func("MyFunc1")
FuncSet("MyFunc2", oFunc) ;or some other syntax
MyFunc2()
oFunc := Func("StrLen")
FuncSet("MyFunc1", oFunc) ;fails because MyFunc1 was resolved at loadtime
MyFunc3() ;fails because MyFunc3 is undefined
MyFunc1()
{
MsgBox(A_ThisFunc)
}
Hypothetically, I don't see any benefit in limiting it to global functions, only drawbacks. If a function definition creates a variable, it should create a variable in the current scope. Putting scope aside...Maybe explicitly defining a top-level/non-nested function could just automatically generate a super-global variable of the same name containing a function object reference?
... but allow it to be shadowed by an explicit (local) declaration or force-local. Attempting to assign to a constant would be an error.For Func("myfunc") references, the Func("") wrapper is implied whenever an explicit myfunc function is defined
That was the idea.However, I'm wondering if it might be best implemented as a existence-checked alternative to, and not a replacement for, Func("myfunc")?
Code: Select all
global myfun := () {
MsgBox "This is a full function that returns another function."
return 42
}
Probably right that it would be best at all levels of scope. My first-pass thinking went along this line: Since "non-super" globals have to be declared in functions to be used, we wouldn't want to have to make a global declaration in every function where we wanted to reference a function name ==> For simplicity of implementation / explanation we'd want to make it use some structure/mechanism that already exists in the language rather than making it somehow "special" (except for one aspect: its auto-generation in a script that has #FuncSuperGlobals "On") ==> Since super-globals can only exist at the top level, I wasn't smart enough to figure out a way to quickly make it work at all levels, let alone with methods ==> Since much/most FP code (especially for quick stuff like AHK macros) has functions at the top level, this would get us most of the functionality we need -- but as a completely optional auto-generator directive using a language construct that already exists. If it could be done at all scope levels, but without having to make a statement like global myfunc1, StrUpper, myfunc5 before use in every function, that would be great!Hypothetically, I don't see any benefit in limiting it to global functions, only drawbacks.
Agreed. Not ideal, but if off by default and only enabled by #FuncSuperGlobals "On", those using it would just need to know what they're signing up for and that it's simply an auto-generator of super-globals. For everyone else, there's no change to behavior.A user won't accidentally cut off access to a function, but it isn't intuitive... Anyone else can name variables without regard to what functions exist, but only if the variables are always initialized; a function reference might creep through and cause unexpected behaviour.
Agreed as well. As long as one could still shadow the constant, seems like this makes better sense. I just thought that maybe harder to implement because it introduces more special constructs like constants that don't currently exist.It would be much safer for each function definition to introduce a constant that returns a reference to the function... Attempting to assign to a constant would be an error.
I've often wished for lambda blocks a la Java, Ruby, Kotlin, etc., but because of multi-expression and ternary, I can usually force most things (except flow control) into a lambda. It would be great if expression/lambda blocks eventually came to AHK. As for the issue you mention, I could then make all my functions consistently variables, but I'd have to create variable definitions for the standard library functions. I often mix these in with my own functions, like: revupper := compose(StrUpper, reverse) or mapf(StrUpper, mylist).Perhaps that would solve part of your problem, by allowing you to make your functions and functors consistent; i.e. all variables?
What a Pandora's box you opened, with all these functional programming yahoos coming in and asking for way more non-trivial features! I really was trying for trivial (albeit a little hackish) with this alternative. No matter what... fat-arrow is still great even the way it is. Again, @Lexikos, thanks again for all the time you put into this. You put so much careful thought into all these decisions and into the level-headed moderation of a great and very much under-hyped language. We who are in-the-know about it should be grateful we have the pleasure to write in it!We have fat arrow functions now because I realized they were relatively trivial to add to the current implementation...
Local variables are automatically accessible to nested functions, the same as super-global variables. Whether the functions are super or not at the global scope has no relevance to the local scope.Since super-globals can only exist at the top level, I wasn't smart enough to figure out a way to quickly make it work at all levels
Code: Select all
iseven := func("isdiv").bind(2)
funcfromobj("isdiv", "isdiv") ;add a new function to the function namespace
msgbox(iseven(6)) ;allow undefined functions at loadtime
;note: funcfromobj could have a scope parameter
;note: myfunc() could be optimised if the function exists at loadtime,
;or undefined and added in later otherwise
You could add an Options parameter to Func, so that it did raise an error.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.
Code: Select all
; Function
AutoClicker() => MouseClick('Left')
; Function Object
AutoClicker := () => MouseClick('Left')
Code: Select all
; I've defined a function and function object name here.
AutoClicker := AutoClicker() => MouseClick('Left')
Code: Select all
AutoClicker := () => MouseClick('Left')
AutoClicker.callable := 'AutoClicker'
Code: Select all
AutoClicker := () => MouseClick('Left')
AutoClicker() => %AutoClicker%()
Code: Select all
isDiv(y,x) => mod(x,y) == 0 ;is divisible by
neg(f) => x => !%f%(x) ;negate function factory WISH: this didn't need %%
isEven(n) => %func("isDiv").bind(2)%(n) ;is even WISH: this didn't need func("") here
isOdd(n) => %neg(Func("isEven"))%(n)
MsgBox isOdd(3)
Code: Select all
; parameter n is hidden (original by sirksel)
isodd := neg(iseven)
; parameter n must be made explicit
isOdd(n) => %neg(Func("isEven"))%(n)
Code: Select all
neg(f) ::= x => !f(x) ;negate function factory WISH: this didn't need %%
Code: Select all
isDiv(y,x) => mod(x,y) == 0 ;is divisible by
isEven ::= func("isDiv").bind(2) ;is even WISH: this didn't need func("") here
; Original
; isEven(n) => %func("isDiv").bind(2)%(n) ;is even WISH: this didn't need func("") here
Code: Select all
isDiv(y,x) => mod(x,y) == 0 ;is divisible by
isEven ::= isDiv(2) ;is even WISH: this didn't need func("") here