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

Discuss the future of the AutoHotkey language
iseahound
Posts: 465
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

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

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.
Helgef
Posts: 4002
Joined: 17 Jul 2016, 01:02
Contact:

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

04 Oct 2019, 10:35

Code: Select all

class f {
	static call() {
		msgbox a_thisfunc
	}
}
%f%()
iseahound
Posts: 465
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

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

04 Oct 2019, 10:42

Is something like this still possible?

v1

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*)
   }
}
Helgef
Posts: 4002
Joined: 17 Jul 2016, 01:02
Contact:

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

04 Oct 2019, 10:58

I'm not sure we are on topic now. I guess something similar could look like this,

Code: Select all

namespace.s()

class namespace {
   class s {
      static call() {
         MsgBox A_ThisFunc
      }
   }
   static __Call(name, terms) {
		return %(this.%name%)%(terms*)
   }
}
lexikos
Posts: 6653
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

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

04 Oct 2019, 18:51

@iseahound
There is no part of your post (on page 1) that is correct.

The functor class was just boilerplate code that is no longer needed.

It was never possible to call the class (or any other variable) directly as though it were a function. (Edit: You can call it indirectly as a method of some other object, as demonstrated above.)

"Functions passed by reference" are just Func objects, passed by reference. A function reference is just a reference to a Func, just as an Array reference is a reference to an Array, and a variable reference is a reference to a variable (but a variable isn't an object). Functions are normal "first class" objects; the syntax required to invoke them is not special, but common to all objects.

Fat arrow syntax is just syntax sugar for defining a function and retrieving its reference. In fact, it returns a reference to a Func or Closure. Would you say that fat arrow syntax is "special syntax" and second class?

All syntax is special, or all syntax is not special; it's not much of a distinction. Func("funcname") isn't special syntax; it's a function call. You just want function references to have syntax in common with variables rather than function calls.
Last edited by lexikos on 04 Oct 2019, 18:54, edited 1 time in total.
User avatar
jeeswg
Posts: 6829
Joined: 19 Dec 2016, 01:58
Location: UK

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

04 Oct 2019, 19:33

This is not a personal priority of mine, but is there any reason we couldn't do something like this? (That is if we permit non-existent functions at loadtime.)

Code: Select all

oFunc := Func("StrLen")
FuncSet("Func1", oFunc)
MsgBox(Func1("abc")) ;Error:  Call to nonexistent function.
@sirksel: Are these solutions any good for you?

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*)
}

;==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
iseahound
Posts: 465
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

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

04 Oct 2019, 22:24

lexikos wrote:
04 Oct 2019, 18:51
You just want function references to have syntax in common with variables rather than function calls.
That's true. But I can settle for a bit less. Either:
  • If Func() is called on a function producing a function reference, have the function reference affect the function itself. For example, binding an argument to a function should modify all future calls of that function.
  • Provide some way of turning a function reference back into a function. For example, the ability to associate a function to a function reference.
I prefer option 2, mainly because once a function has been modified with an bound argument or something, it's likely to be a permanent change and the function reference would never be used again. Keeping the function reference around is perfectly fine, but forcing users to call the function through the function reference is strange.
EDIT: which jeeswg just proposed!
sirksel
Posts: 53
Joined: 12 Nov 2013, 23:48

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

04 Oct 2019, 23:44

I'm a little confused by some of the discussion/proposals. Here's the usual functional-programming use case (I think), where short functions are used to create other functions:

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))  
Maybe I'm missing what's being discussed, but I don't think I would want isdiv(y,x) to be permanently altered by the binding that created iseven. The whole point of FP is that I can partial/bind, compose, and otherwise re-use functions to create other functions.

I was just proposing some possible interpreter inference that wouldn't require the full Func("") reference, unless there in ambiguity in the namespace. Similarly, would love flexibility to leave off the %% for a call where there is no ambiguity in the namespace. I guess that might be what Lexikos meant when he said "more like variables"? I think I might be asking for something less complicated that all-out merging of namespaces or wholesale changes to the function object itself? Lexikos, when you have time, could you please let me know if you understand what I was asking in my earlier posts and if it's at least in the set of things you're considering? If it's not workable or not of interest to you, I completely understand. Thanks so much!
lexikos
Posts: 6653
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

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

05 Oct 2019, 01:29

sirksel, I think we are on the same wavelength, unlike some others.
sirksel wrote:
04 Oct 2019, 07:46
I guess it just seemed to me like derived functions are more of an advanced concept, so those who use them will probably understand concepts like shadowing in the namespace.
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.
Maybe the following might be an implementation alternative that keeps the namespaces separate, but instead introduces a couple of new syntax inferences:
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.

As I said, I'm still considering options. I believe there are options I haven't considered yet.

One idea (that I thought had been brought up already, but I'm not sure now) is to declare that a variable can be called. This would allow load-time validation and optimization of functions to be retained. For example:

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
I suppose the main problems are:
  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).
  2. It doesn't translate well to objects (although an object can permit properties to be called via __call).
  3. Deciding on a name for the declaration.
It would not be my intention to implement any kind of static type-checking. The declaration would have a similar effect to iseven(a*) => %iseven%(a*), except that Func("iseven") might fail or return the value of iseven.
User avatar
jeeswg
Posts: 6829
Joined: 19 Dec 2016, 01:58
Location: UK

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

05 Oct 2019, 01:43

- So, is the latter feasible? In the example below.
- Are there any advantages/disadvantages to the 'FuncSet' approach below? Am I missing something?
- It seems to be the cleanest approach, and matches sirksel's main request.
- We already have a separate function namespace, so why not make the most of it?
- For greater security, you could have a mode such that FuncSet would only create a function if the name was not already in use, and would throw otherwise.

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
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
sirksel
Posts: 53
Joined: 12 Nov 2013, 23:48

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

05 Oct 2019, 04:15

Lexikos, thanks for the reply. What you're saying makes complete sense, and yes, I realize unintentional "use" of what I proposed could be a problem for some. (That's why somewhere I think I mentioned a compiler warning, on by default -- but I get it... that's messy and probably not that helpful to beginners.) Plus, it seems like your decisions not to mix namespaces have worked out really well and made for a very clear/clean language. As you point out, this is not one of those situations where mixing is the only alternative.

Your callable solution seems great, and it addresses the most important part of what I was getting at. In many of the languages (at least those I'm familiar with) that support the kind of lambda/partial/first-class-function/succinct-syntax awesomeness you've now built into AHK, they all do seem more or less agnostic (at least at the most basic level) about how to deal with a callable. In other words, one doesn't have to know much about how the callable was created to call it -- thereby enabling a lot of general-purpose code. Sure, for example, in Python, you can introspect to find the particulars of the "partial" object that the partial() function returned to enable subsequent lazy evaluation of the closure -- but you don't need to. A callable is a callable, and the basic interface of how to call it, how to inquire about its parameter requirements, etc. is the same for every callable. It seems like your solution does this.

That's why, if I had to prioritize, I'd say the %%() is more important than the Func("") wish. There are many more of the former than the latter in my FP code, since that happens at every invocation of a derived function. However, to your point about the mechanics of the callable declaration:
The declaration would have a similar effect to iseven(a*) => %iseven%(a*), except that Func("iseven") might fail or return the value of iseven.
...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.

I also like that assigning a non-callable to a callable variable is not necessarily an error. I do often overload parameters that typically get a callable (say, a predicate function) when an integer or string passed in that position might tersely auto-generate the most common predicate.

All that said, I'd still love to come up with a more agnostic/compact syntax for getting function references. I'll give it some thought. compose(func1, Func("func2"), func3, func4, Func("func5")) seems just so difficult to read/write when you're composing, piping, or doing whatever to a mix of explicit and derived functions. I suppose, if the above paragraphs hold true in your implementation, you could always do: compose(Func("func1"), Func("func2"), Func("func3"), Func("func4"), Func("func5"))... but it's so ugly compared to all your other terse/streamlined FP-enabled constructs. For that matter, we could embed the Func() call in compose, to essentially compose functions by name: compose("func1", "func2", "func3", "func4", "func5"), but that bypasses the Func() optimizations, right?

Also, I didn't fully understand what you meant by #1 in your list of problems to consider (specifically "both ways on its own" and the bit about the "auto-execute section"):
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).
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.
lexikos
Posts: 6653
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

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

05 Oct 2019, 05:37

Functions may be nested, in which case Func() will only return them if called within the same scope.

If Func("func2") returned the value of a callable variable, the same would apply to local variables.
jeeswg wrote:(That is if we permit non-existent functions at loadtime.)
This is a problem. Load-time optimization would pose a problem for your idea, as function names are resolved to addresses.
User avatar
jeeswg
Posts: 6829
Joined: 19 Dec 2016, 01:58
Location: UK

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

05 Oct 2019, 05:46

- @lexikos: Thanks. I have some responses although accept that there may be further hurdles I haven't thought of:
- A function that added functions to the function namespace, could have a parameter to specify to do so at the global level.
- The way I see loadtime, is this: any time we see a function call, add the function name to the function table; if we see a function definition, add the function name and address to the function table. We have gaps for undefined (future) functions, and the functions we did see, they are optimised. The future functions would be as optimised as any function reference created via the Func function.

- [EDIT:] An example:

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)
}
Last edited by jeeswg on 26 Oct 2019, 07:14, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
sirksel
Posts: 53
Joined: 12 Nov 2013, 23:48

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

26 Oct 2019, 06:11

As I've been adjusting a large functional code base for the latest alpha and rereading the v2-thoughts file, I've given this topic more thought. I know you said, @Lexikos, that having a plain reference myfunc imply essentially Func("myfunc") -- if a variable of that name is not in scope and an explicitly defined function is -- would be a prioritized mixing that could be confusing to beginners. As I was thinking about it more, maybe it's not much more complicated than the notion of shadowing or not shadowing a super-global, which even beginners must contend with if they define one?

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? So rather than mixing/inference, there's actually a super-global implicitly defined that can be shadowed, overwritten, etc. just as any other super-global. Maybe this behavior is not on by default (so as not to trip up beginners), and one would have to use #FuncSuperGlobals "On" to get the behavior? Functional programmers would likely use it to enable parallel references to explicit and derived functions in their function compositions -- e.g., compose(derivedf1, Func("explicitf2"), derivedf3) becomes compose(derivedf1, explicitf2, derivedf3) -- but others might not use it at all. @Lexikos, could that work? Also, are classes still essentially super-globals too, even with the latest object changes?

(Currently, as a workaround, I have a code section that manually defines a super-global for every top-level explicitly defined function, but it violates DRY (don't repeat yourself) and offsets at least some of the elegance/streamlining that all the other great syntax sugar like fat-arrow afforded functional programmers.)

I think as a backup alternative to that, the @fincs idea you mention in v2-thoughts of @myfunc could still improve readability without much more confusion. However, I'm wondering if it might be best implemented as a existence-checked alternative to, and not a replacement for, Func("myfunc")? I wouldn't want to lose the ability to dynamically reference a function object, like: Func("myfunc" num). That said, nearly all of these references in my code are just straight string literals, and so using @myfunc instead of the full Func("myfunc") would greatly improve brevity/readability in my FP use case. It's definitely a second choice for functional programming though, because although compose(derivedf1, @explicitf2, derivedf3) is more readable, the derived and explicit functions are still referenced differently, and one has to remember which is explicitly defined and which is derived.

P.S. I'm still thinking about alternatives for the object-calling syntax of %func%() syntax or a callable declaration or what not, which was the other half of my functional-programming plea...

Thanks for your time and consideration.
lexikos
Posts: 6653
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

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

27 Oct 2019, 03:45

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?
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...

I think the difference from nnnik's suggestion is that this would be a separate variable, and as such assigning it a new value would be possible but would not affect function calls. A user won't accidentally cut off access to a function, but it isn't intuitive. The function and variable would be in sync only until an assignment occurs, which may cause confusion for those using the function reference intentionally. 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.

It would be much safer for each function definition to introduce a constant that returns a reference to the function, or in other words, use the first part of one of your previous suggestions:
For Func("myfunc") references, the Func("") wrapper is implied whenever an explicit myfunc function is defined
... but allow it to be shadowed by an explicit (local) declaration or force-local. Attempting to assign to a constant would be an error.
However, I'm wondering if it might be best implemented as a existence-checked alternative to, and not a replacement for, Func("myfunc")?
That was the idea.


We have fat arrow functions now because I realized they were relatively trivial to add to the current implementation, but my intention (not necessarily for v2.0) is to permit full function definitions in expressions. Perhaps that would solve part of your problem, by allowing you to make your functions and functors consistent; i.e. all variables?

Code: Select all

global myfun := () {
    MsgBox "This is a full function that returns another function."
    return 42
}
sirksel
Posts: 53
Joined: 12 Nov 2013, 23:48

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

27 Oct 2019, 07:53

Thanks @Lexikos for the consideration. Here are some further thoughts/clarifications based on the topics you raise:
Hypothetically, I don't see any benefit in limiting it to global functions, only drawbacks.
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!
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. 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.
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.
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.

Off-Thread Topic: As long as we're talking about constants, I really think these would be a great addition to the language, if you're so inclined. In almost every library, I have a large set of super-globals that serve as constants, but I can't really protect them from accidental assignment. Also, it would be a fantastic follow-on if a default parameter could then be assigned any global constant. I constantly use f(x, y:='`v') where vertical tab serves as a "missing" sentry that can then be "forwarded through as missing" to other function(s) called. Something like f(x, y:=NA) would be so much clearer in code./End Off-Thread Topic
Perhaps that would solve part of your problem, by allowing you to make your functions and functors consistent; i.e. all variables?
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).
We have fat arrow functions now because I realized they were relatively trivial to add to the current implementation...
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!
Last edited by sirksel on 29 Oct 2019, 05:38, edited 1 time in total.
lexikos
Posts: 6653
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

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

28 Oct 2019, 04:05

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
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.

Return to “AutoHotkey v2 Development”

Who is online

Users browsing this forum: No registered users and 3 guests