Order-independent function/constant declarations?

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

Order-independent function/constant declarations?

Post by sirksel » 12 Mar 2023, 18:03

I've been loving v2 since it went mainstream (thanks @lexikos)! As I've been converting code, I've been thinking a lot about AHK and comparisons with other languages in which I regularly write, especially with regard to first-class functions and definitions. It seems to me that a weakness of a lot of languages is the inability to declare a derived (but otherwise constant) function without being dependent on order of execution.

For example, in AHK, the following global scope lines could be declared in any order:

Code: Select all

isDiv(x,y) => Mod(x,y) == 0   ;is divisible by
isEven(x) =>isDiv(x,2)  ;is even number
isOdd(x) => !isEven(x)  ;is odd number
But as soon as one starts employing derived functions, things quickly become order-dependent:

Code: Select all

isDiv(x,y) => Mod(x,y) == 0   ;is divisible by
neg(f) => p* => !f(p*)  ;negates truthiness of f result
;the following must be defined in this order, and before they are used
isEven := isDiv.Bind(,2)
isOdd := neg(isEven)
If there were a way for these definitions to be labeled constants (maybe signified by const or def or val or some other keyword?) it seems like the interpreter could make sense of them in any order. With formal function definitions (like my first example), aren't these definitions all located during a first pass? I don't know the interpreter workings well, so I could be wrong in this impression. Anyhow, could some constant keyword prefix allow these types of immutable definitions to be identified in a first pass?

It seems like this might also be generalizable to non-functional constants, especially helpful for those that cross-reference but are declared in an otherwise alphabetic order (admittedly trivial example, but this ordering wouldn't work for obvious reasons):

Code: Select all

;alphabetic list of global constants
lb := [rl, lf, rt]   ;list of possible line breaks (out of order)
lf := "`n"  ;linefeed
pi := 3.14159   ;  :)
rl := rt lf  ;return-linefeed (out of order)
rt := "`r"  ;return
Could some prefix allow these to be located in the first pass and therefore not order-dependent?

I know these examples are trivial. In my real libraries it's a little more complicated, but I've tried to use .Bind and function wrapping/currying wherever possible in terse/DRY functional style. However, this often makes editing the code a pain, since (1) derived functions can't generally be listed in alphabetical order, (2) inserting new functions often involves repositioning definitions in the right place, so all dependencies are physically above their descendants.

There are definitely places where I use partial/bound functions or variables that are indeed temporary or mutable and *should* be order dependent. It's just there are a whole lot of cases where it's about cross-referenced global constants or functions that do not change within a library.

Is this something you have considered or would consider? Many thanks for all the hard work! No matter what, AHK is still my favorite language for automation and quick prototyping. :)

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Order-independent function/constant declarations?

Post by iseahound » 13 Mar 2023, 08:02

Interesting. I suppose the main motivation would be to keep the implicit ordering of parameters used by :=? Because if you didn't mind restricting yourself to =>:

Code: Select all

isDiv(x,y) => Mod(x,y) == 0
neg(f) => (p*) => !f(p*) 

isOdd(z) => neg(isEven)(z)
isEven(x) => isDiv(x, 2)

msgbox isOdd(5)
msgbox isOdd(6)
JavaScript calls this hoisting by the way.

sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Order-independent function/constant declarations?

Post by sirksel » 13 Mar 2023, 11:16

Thanks for the info. What you described above is a good idea. It's actually what I do today -- either that or my first example in the OP. It's just that once you extend this technique a lot, you have a lot of unnecessary function calls, where you could have just been binding to existing function. Who knows, under the hood, maybe it results in exactly the same number of function calls, or it's premature/needless optimization?

I guess for me, it's more that the functional style (which is very nicely enabled in AHK in all other respects, except for this issue), leads people to "compose" or chain or curry or other techniques, these existing first-class function objects into new functions, rather than just calling them. It may be semantics and I just need to get over it already... :)

The JS hoisting parallel you mentioned is really interesting. I was thinking JS hoisting only dealt with the declaration, but not the definition or initialization of the variable. I was trying to see if JS differentiates this treatment between constants and other variables. In this case, I'm kind of emphasizing I'm thinking about this only for the functions that never change, just like the fact that full function definitions never change (and actually can't be overwritten, which I think was part of the "global" changes in late v2 alpha). It would certainly be less convenient, if like in C, we had to declare all full functions physically above where they are used. But, as you point out, this treatment is really based on whether your function is was defined with { } or => as opposed to := (which I know are fundamentally different processes under the hood).

You raise an interesting point about symbols. This issue only really comes up with fat-arrow type functions, where the rhs is an expression. I had been thinking about the more general question of how to mark a specific := as unchanging, with const or whatever, so it could be handled in the first pass. Maybe an alternate approach would just to have an alternate => symbol (maybe :=>) that says that the rhs is not a return value to resolve at call, but instead an unchanging function object to be assigned at declaration? I know it's always tough to make the case for new symbols, as it complicates the parsing and makes the language harder to read/learn.

Anyhow, I appreciate you giving it some thought. Your comments broadened how I'm thinking about it now. Many thanks.

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Order-independent function/constant declarations?

Post by iseahound » 13 Mar 2023, 12:18

The reason I mentioned JavaScript is because Lexikos tends to use that as an inspiration for AutoHotkey. I think const has been suggested a few times on the forum.

Code: Select all

f(a) => a + 2
f.call := (a) => a + 6 ; Read-only property
It might work with the current mechanics of fn(){} and fn() =>. But as variable declarations are hoisted, I don't think your out of order assignment statements would work, as the actual assignments occur in order, and it likely doesn't work in JS either.

Note: rhs means right hand side, as in the right side of := or => for those who are reading this and don't like abbreviations :)

sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Order-independent function/constant declarations?

Post by sirksel » 13 Mar 2023, 14:31

I understand. Thanks for that reminder too. You're definitely right about this coming up before.

Several years ago, I asked about constants in the context of parameter defaults. Lexikos ended up giving us the Cadillac version of parameter defaults... more than I had even hoped at that time. (I just realized recently that you can even make a parameter default to another parameter. So great!)

Then, a couple years ago, I brought constants up with respect to functions, but without the clarity that I have now about how it's really just a general-purpose constant, and not specific to callables. It's so funny that you guys have a lot more clarity about the direction of these things, and I'm often fully appreciating your logic/vision much later on. Here's what Lexikos said at the time:
User defined "constants" are planned. Making syntax specific to "callable" constants doesn't make a lot of sense. Any constant is callable if its value has a Call method. For syntax highlighting and similar: It is possible through static analysis to identify which variables are directly assigned a function by name or fat arrow syntax, and it should be easier for constants as they would be guaranteed to have only one assignment/initializer.
Later in that post he also mentions how it might or might not fit in with Helgef's work on modules. Maybe since there's been recent work considering modules, there might be somewhere that constants fit into the mix.

I didn't fully appreciate it at the time, but it all makes more sense to me now. Apparently, you guys have had that clarity and vision all along. Sometimes I have to flail around in my own code a bit before I see the light! :) Thanks again.

EDIT / P.S. / Slightly off-topic: For others reading, I realize my examples are trivial, but functional programming style is more that isDiv(), isEven() and isOdd(). Here is an article about how compose and point-free as examples of functional style can work in JavaScript. To my knowledge, even in JS, unlike true functional programming languages, the declarations are dependent on execution order. If AHK gets a good const solution to allow unordered declaration, it will actually be a better functional programming language than JS!.... P.P.S. I was surprised to find that even CS51 is now taught in OCaml (instead of C++ when I was in college), at least in part due to its functional programming capabilities. OCaml is a flavor of ML, a functional programming language which has been around for 50 YEARS! Funny how functional is starting to get some love again in the last decade or so.

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Order-independent function/constant declarations?

Post by iseahound » 13 Mar 2023, 22:09

I'm pretty sure a class being taught in OCaml is just Jane Street trying to recruit new graduates... Also, I think ML stands for machine learning instead of meta language now :P. Wasn't aware that parameters could default to other parameters either.

I wanted to clarify my previous statement, so when variable declarations are hoisted, out of order assignment statements would not work, as the actual execution of assignment occurs in order. (Because declaration != execution. An analogy: playing a card in a game doesn't mean your card can activate its effects immediately, your opponent has a chance to respond.)

Python - Doesn't check functions until execution

Code: Select all

def f():
   pwodjpaojdpwoajdp

print("No errors!")
Javascript - Doesn't check functions until execution

Code: Select all

function f() {
   awdawfawfafw
}

console.log('No errors!')
However, AutoHotkey does check functions and the following will throw:

Code: Select all

f() {
   awdawd
}

msgbox 'hi'
So AutoHotkey has stronger error checking, which might make some of your suggestions more difficult.

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Order-independent function/constant declarations?

Post by iseahound » 19 Mar 2023, 11:59

So to continue my previous post, I never explained why "checking functions after / before execution" was relevant.

It has to do with the declaration of the special variable this, the very core of object oriented programming.

#1 - Defining a class in AutoHotkey. Note how the appearance of this is implicit inside the method body.

Code: Select all

class cls {

   static value := 42

   static method() {
      return this.value
   }
}

MsgBox cls.method()
#2 - Here is a much more interesting example. Notice how defining an object in AutoHotkey that contains methods suddenly requires an explicit (meaning must be made obvious) declaration of this?

Code: Select all

cls := {value: 42, method: (this) => this.value}

MsgBox cls.method()
#3 - Here's Python's definition of a class using explicit this variables. (Python likes to be different so it uses the 4 letter word self.) The idea is that an explicit declaration is more clear than an implicit declaration. And by providing clarity, it becomes easier to reason about the underlying code. (Or so Python claims).

Code: Select all

class cls:

    value = 42

    @classmethod
    def method(self): # most people use cls here, but I don't want to use the name of the class
        return self.value

print(cls.method())
#4 - So part 4 is about creating an object in python that can return one of its own values... and this is a little unintuitive for Python users I think.

Code: Select all

cls = type('some_class_name', (object,), {'value': 42, 'method': lambda: cls.value})

print(cls.method())
So the main reason why a function might not want to check its validity until execution is usually to permit recursive definitions. Instead of admitting an explicit this/self object, Python forces you to use the name of the object you just defined inside the anonymous fat arrow / lambda function.

My Javascript isn't as good but here we go:

Code: Select all

cls = {value: 42, method: () => cls.value}

console.log(cls.method())
(Is there a better way of doing this here?) 2023-03-20: Yes, see https://www.digitalocean.com/community/tutorials/understanding-arrow-functions-in-javascript#arrow-functions-as-object-methods. The basic idea is that this is a keyword that refers to the lexical scope, and has nothing to do with being passed, either explicitly or implicitly as a parameter. So if I used function() {} instead of () => {} I would have a valid this. Oh my god it gets worse, if this falls out of scope it will then belong to the global window object. https://www.freecodecamp.org/news/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881/
Last edited by iseahound on 20 Mar 2023, 15:39, edited 2 times in total.

sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Order-independent function/constant declarations?

Post by sirksel » 20 Mar 2023, 02:19

Thanks for the explanation. I think I understand, but possibly not.

#1: I get for sure. Basic class stuff.

#2: I understand too. I always have to remember to include f(t,p*) when using DefineProp, with t or this in first position.

#3: This is where I get a little lost. I get the explicit naming of self, but does that mean Python checks function bodies in the same or different manner than AHK?

#4: Interesting. I'm not entirely sure I understand this either, but it seems like this is an example of one method calling another method of the same class. Is it recursive or just names itself to get another method?

AHK gives me no issues with code like the following, where I'm referring in a getter/method function body to a getter/method yet to be defined:

Code: Select all

class a {
}
a.Prototype.DefineProp('early', {Get: t => 'before ' . t.late})
a.Prototype.DefineProp('late', {Get: t => 'after'})
o := a()
MsgBox o.early '`n' o.late
Or even where I'm referring to a function yet to be defined:

Code: Select all

early := () => 'before ' . late()
late := () => 'after'
MsgBox early() '`n' late()
I believe this is because while some symbol/name checking happens at script startup (the subject of your earlier post), the function bodies themselves don't have to be valid or fully qualified or in scope or whatever (sorry for the imprecise language) until executed. Is that right?

My problem comes in when, because of functional programming composition or partial function binding of arguments or what not, my declaration calls a function to define a function. Like the isOdd := neg(isEven) from earlier. I think because the neg(isEven) actually executes at declaration time, isEven must have been defined earlier. It doesn't go and look for it. I think that's because there could be more than one definition, correct? In other words, since isEven's value can be changed, depending on the assignment's position in code, there could be completely different meanings of isEven.

However, if isEven were a constant (assuming AHK supported them), the interpreter would be guaranteed exactly one definition. So it would be possible for the interpreter to go find it... in the same way it does when "normal" {} function bodies are defined out of order. Am I thinking about that right?

I'm trying to think through your comment on recursive functions.... Even if a function definition were recursive, if it only has one definition (i.e. in traditional function {} definitions or in a constant := assignment, in a world where constants are supported), it seems like recursion is still possible and doesn't need to be checked differently for a constant assignment vs a traditional function {} definition. This is the part I guess I don't fully understand.

I appreciate you considering this and helping me understand better. You all in the AHK community are so nice this way! Many thanks.

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Order-independent function/constant declarations?

Post by iseahound » 21 Mar 2023, 21:28

That is a pretty amazing discovery!

So that begs the question: Could what you are proposing be done? I think ultimately Lexikos knows better, as I can't really provide a formal definition at this point.

Code: Select all

glob := () => kiki
foo := () => baz
fn := () => bar
bar := () => foo 
baz := () => glob
kiki := () => 123

msgbox fn()()()()()()
Here's playing around with it further. As long as you restrict yourself to solely fn := () => (), fn() => (), or fn() {} you shouldn't have any problems! It seems that when the parser looks for functions, it places them in their own "priority class". So if you avoid crossing classes (meaning don't mix := and =>), it should be fine! Or if const is ever introduced to hoist (cross classes) assignments.

Yep and this works fine too :superhappy:

Code: Select all

cls := {value: 42, method: (*) => cls.value}
MsgBox cls.method()
But that leaves the open-ended question of := unsolved, since it's clear from your first post that := and := () => are two different things.

Post Reply

Return to “AutoHotkey Development”