Nothing.

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

Re: Nothing.

Post by iseahound » 12 Sep 2024, 05:02

I'll just clarify my point more formally (or to confuse people further?)

If the K-complexity of the function that changes the array indexes is equal to the function itself, then it should be an UnsetError.

So here the k-complexity is the kolmogorov complexity or the smallest function that can produce the intended array. In other words, a normal function might restrict its values from 1-100 and that initial guard or check would be a ValueError. This if statement that guards access to the rest of the function can be separated from the actual function itself. However checking the length of an array can't really be a guard since the length of the array is related to the algorithmic complexity of the actual function. Since checking the index / length of the function would be equivalent to just modeling the function itself, it acts the same way as a key to a dictionary/map would.

So after writing this all out, I suppose the easiest way to explain it is to say: If you can separate the allowable types from the function then throw a TypeError. If you can separate the allowed parameters throw a ValueError. But if a separation between input and allowable values isn't possible because the two are codependent, then you're not really guarding anything! You are just restricting access to the function.

Cebolla
Posts: 43
Joined: 17 Feb 2024, 17:27

Re: Nothing.

Post by Cebolla » 13 Sep 2024, 01:56

Descolada wrote:
12 Sep 2024, 04:02

I disagree that null/unset passed to a function should produce no change. Such code should throw an error, otherwise it'd lead to hard-to-debug problems in the future (as AHK v1 did with silent fails). Either your array must have values at all indices, or your function should be able to handle unset, pick one. What is the objective of leaving an empty hole in an array anyway?
I'm only a novice, and have no formal skills nor training in any IT adjacent field. But I can imagine a scenario why I would want to have an empty spot in an array that gets passed over when being enumerated. Say I'm using an array of arrays to represent a table structure, where each index in the row vector is associated with a column header at index 0. The position of each item is now significant.

Another example would be in the case of a large dataset that uses relative position to some degree of significance. For example, defining an improved search/lookup algorithm for large data. When traversing the dataset, and constructing a new dataset, you leave empty space between set values to account for future additions. Like, sorting large data alphabetically. If you built your new dataset using a linear approach, going back and adding an unexpected "B" item when you're already at your 1000000th item and in the "S"s is going to take a hot minute.

An array was a very bad choice of example for me to use to illustrate my preference. My use case for `null` is about named variables/properties; arrays are explicitly used when names are unneeded or unknown.

I do agree that having the possibility of the program to throw an error when referencing a null value is a good thing. But if my program were to benefit from no errors being thrown, I'd like to be able to turn the hypothetical feature off.

My `null` class will throw an error if an operation is performed upon it a variable that references the class. In the way I plan to use it, this is actually a good thing and fits the use case well. The purpose of `null` is only to allow an unset property to exist in the namespace of my code so I can use it as a reference when validating user input. My other options were to copy-paste the property names into a separate object, which gets a big no from me. Or to use a `0` or `false` value, which is fine, not a big deal, would cause literally zero issues, but its not what I want 😂 I want significant values to have significance! False is a pretty big deal, it's 50% of all possibilities! Can't be throwing around big names like that all willy-nilly. And zero is the most unique number in all the numbers. Nothing else is quite like zero, so surely we should save it for cases when zero is needed.

In my mind, `null` is the preferred name for an unset value. I also like `unset` and I use it extensively in my AHK code. But it's just not my ideal. No shade cast to the hard work done by people much more knowledgeable and talented than me - I have been having a blast learning with AHK

@Descolada thank you for the shared code 👍 I appreciate the consideration`

User avatar
xMaxrayx
Posts: 367
Joined: 06 Dec 2022, 02:56
Contact:

Re: Nothing.

Post by xMaxrayx » 15 Sep 2024, 12:44

what does Unset solve other than save some MB? i'd rather save my time than saving less than 1MB if it wasn't 1kb in 16GB Ram minimum requirements era.

the funny thing that you MUST double "IF" checker if used variable with unset in class; one for "hasownproperty" and another If for your thing.

which is just waste of time. id rather learn soldinrg my ram if I need to upgrade it if i was that poor at least more fun than spam writing same words :terms: , I don't use C++ here :beer: , I want use script that can be written fast and edited fast .



Cebolla wrote:
13 Sep 2024, 01:56

In my mind, `null` is the preferred name for an unset value. I also like `unset` and I use it extensively in my AHK code. But it's just not my ideal. No shade cast to the hard work done by people much more knowledgeable and talented than me - I have been having a blast learning with AHK

Never , Null is like a third option "user never set the value kind" and take some size on ram wont make the code crash, unset is just destroying the variable.

also you can do this with null

Code: Select all

if x == 1{}
else if x ==2{}
else if == null{}
when with unset you cant do it and you need use if isset first, witch you must add it unless you want break your app.
-----------------------ヾ(•ω•`)o------------------------------
https://github.com/xmaxrayx/

Cebolla
Posts: 43
Joined: 17 Feb 2024, 17:27

Re: Nothing.

Post by Cebolla » 15 Sep 2024, 19:24

Never , Null is like a third option "user never set the value kind" and take some size on ram wont make the code crash, unset is just destroying the variable.
I believe I undertand your meaning 😂 and yes I agree. I want to be able to reference a symbol in my code even if that symbol does not have a value, and not have the interpreter yell at me.
when with unset you cant do it and you need use if isset first, witch you must add it unless you want break your app.
I definitely get your feeling that first checking `IsSet`, then immediately after checking the value of the thing same thing was a bit annoying to get used to, and took me a while to understand why my code kept yelling at me. But after getting used to it, I now feel that the clarity and intentfulness gained from this usage is worth the slight bit of more keystrokes I have to endure to complete the task.

lexikos
Posts: 9811
Joined: 30 Sep 2013, 04:07
Contact:

Re: Nothing.

Post by lexikos » 29 Sep 2024, 02:28

Cebolla wrote:
12 Sep 2024, 01:53

Code: Select all

interval := (startImmediately_or_input.HasOwnProp('interval') ? startImmediately_or_input.interval : interval)

Code: Select all

input := (IsSet(input) ? input : AlignKeyValPairs.default['input'])
These patterns can be reduced with v2.1-alpha.

Code: Select all

interval := startImmediately_or_input.interval ?? interval

Code: Select all

input ??= AlignKeyValPairs.default['input']
It is by design that a value property cannot exist without a value, but the ?? operators permit non-existent properties.


Descolada wrote:
12 Sep 2024, 04:02
My one objection is the syntax of variable := unset. I think this shouldn't exist, because you should be able to assign only values to variables.
In v2.1-alpha.2+, unset is a subset of the "unset expressions" and must be valid everywhere an unset expression is valid. Delete(variable) is fine when the deletion is unconditional, but an unset expression can consist of a long chain. For instance, a := b?.c?.d unsets a if b or b.c is unset. You can write (IsSet(b) && b.HasProp('c')) ? a := b.c.d : delete(a), but requiring the long form for such cases would significantly reduce the value of having optional chaining in the first place. If a chain needs to be split (e.g. to check some other condition), not being able to assign an intermediate maybe-unset result to a variable would make that difficult. Functions and properties can also return unset, and it can be impossible to know in advance whether a value will be returned. If you want to use the value, while also permitting there to be none, you must assign to a variable (e.g. a := (fn()?)).

unset itself can also be used within a conditional expression, such as a := (b ? c : unset). Being required to write such expressions like (b) ? (a := c) : delete(a) is inelegant and burdensome.

A function cannot operate on a variable without explicitly being given a reference. To work as Delete(variable) rather than Delete(&variable), or to work with properties or items, it would need to be a special case within the syntax, like IsSet.

Assignment is much easier to apply generally to variables, properties and items; simpler to implement, no new syntax to document or learn. Why do you believe an assignment must have a value? It's not that a variable can't exist without a value; variables start out in that state, and can be restored to that state.

For a property setter, := unset is actually equivalent to omitting the value parameter, just as though the setter function (or __set) was called with unset as a parameter. The parameter is mandatory if defined with the property setter syntax in v2.0, but can be made optional by defining a setter with DefineProp (or syntax in v2.1).

Now, I wonder what it would mean for a function to return "nothing". I think it'd be something like "void" in C, and it could make some sense in AHK if returning unset would turn the function into a void function.
When you define a function, its return values have whatever meaning you give them, or no meaning if you give them none. The same would apply to a lack of return value.

a := b is an error if b is unset, a := b.c is an error if c is undefined or returns unset, and so a := c() is an error if c returns unset.

Returning unset is valid only in v2.1-alpha.2+. Functions default to returning "" for backward-compatibility, but this can be changed with #DefaultReturn Unset (at the moment). It does not affect built-in functions, which generally behave as in v2.0.

Returning unset doesn't "turn the function into a void function"; it just means that the subexpression which calls the function will have no value. A function can conditionally return a value or unset.


Cebolla wrote:
13 Sep 2024, 01:56
In my mind, `null` is the preferred name for an unset value.
I think what you really mean is that you would prefer both the name and the general concept and behaviour to be like certain other languages which use "null". I gave it a unique name because it is not intended to be a copy of null as seen in any other language. The purpose, semantics and behaviour are different.

Descolada
Posts: 1494
Joined: 23 Dec 2021, 02:30

Re: Nothing.

Post by Descolada » 29 Sep 2024, 10:19

lexikos wrote:
29 Sep 2024, 02:28
unset itself can also be used within a conditional expression, such as a := (b ? c : unset). Being required to write such expressions like (b) ? (a := c) : delete(a) is inelegant and burdensome.
Requiring it to be inelegant and burdensome can be the desired effect if the language wants to discourage unsetting variables, and I can see value in that in preventing errors.
It appears I personally never use such patterns, so perhaps I'm not the best person to question it... I searched through some of my libraries to find code patterns like a := (b ? c : unset) but found none, only had multiple uses of b ? c : unset as a function argument.
A function cannot operate on a variable without explicitly being given a reference. To work as Delete(variable) rather than Delete(&variable), or to work with properties or items, it would need to be a special case within the syntax, like IsSet.
Delete(&variable) is fine, although I'm not arguing to remove the assignment syntax. Adding Delete alongside the assignment syntax would be superfluous, I'm not particularly advocating for adding that either. Being able to unset via assignment was just a tiny irk on a philosophical level, I see the practical value in it.
Why do you believe an assignment must have a value?
Wikipedia article about assignment in computer science states
In computer programming, an assignment statement sets and/or re-sets the value stored in the storage location(s) denoted by a variable name; in other words, it copies a value into the variable.
Unset/null is an absence of value, so by definition it can't be assigned.
When you define a function, its return values have whatever meaning you give them, or no meaning if you give them none. The same would apply to a lack of return value.
I don't think I can see the value (no pun intended) of returning unset from a function. Could you perhaps bring an example of how/where it would be useful, or how you see users potentially using it?
a := b is an error if b is unset, a := b.c is an error if c is undefined or returns unset, and so a := c() is an error if c returns unset.
Yet a := b := unset is not an error, nor is a := (b := unset). A bug?


I have a small question also. Unsetting an item eg in an array has the same effect as Array.Delete(). Unsetting a keys value in a Map has the same effect as Map.Delete(key). Unsetting an object property does not delete the property itself, but instead is passed into the setter method. Is it possible to check whether an object property is "set" without using try..catch?

Code: Select all

a.b := 1
a.b := unset
c ??= a.b ; Error: No value was returned
MsgBox IsSet(a.b) ; Error: IsSet requires a variable
MsgBox a.HasProp("b") ; 1

Class a {
    static _b := unset
    static b {
        get => this._b ?? unset
        set => this._b := value ?? unset
    }
}

Cebolla
Posts: 43
Joined: 17 Feb 2024, 17:27

Re: Nothing.

Post by Cebolla » 30 Sep 2024, 01:15

lexikos wrote:
29 Sep 2024, 02:28
I think what you really mean is that you would prefer both the name and the general concept and behaviour to be like certain other languages which use "null". I gave it a unique name because it is not intended to be a copy of null as seen in any other language. The purpose, semantics and behaviour are different.
Yes, that is what I really meant 😂 good call. I think your implementation of `unset` is great. It makes sense to have a different name.

Cebolla
Posts: 43
Joined: 17 Feb 2024, 17:27

Re: Nothing.

Post by Cebolla » 24 Nov 2024, 22:38

I updated to the most recent version. Being able to conditionally return `Unset` within a property accessor, or in a function, is a work of art. Solved the only problem I had with the original implementation. Great work 👍 To Lexikos and the lead contributors: You all are doing a great job and should be proud to have played a hand in making something so cool and helpful (AutoHotkey).

lexikos
Posts: 9811
Joined: 30 Sep 2013, 04:07
Contact:

Re: Nothing.

Post by lexikos » 13 Dec 2024, 21:07

Descolada wrote:I don't think I can see the value (no pun intended) of returning unset from a function.
There literally is no value! ;)

Most of the reasons relating to unset variables and parameters apply equally to return values.

Variables defaulting to unset instead of "" improves error detection. Attempting to use a nonexistent value causes an error to be thrown, whether the value was supposed to come from a variable or from a function call. Functions don't default to returning unset yet, but one can do it explicitly for similar reasons.

Parameters defaulting to unset instead of a value allows some ambiguity to be eliminated. Compared to the alternative - making the author choose a unique default value - it improves consistency between scripts. In v1 we had built-in functions which returned an object or "" and others which returned an object or 0, but now we can have functions which return something or nothing.

The practical difference between returning unset and returning "" (or "not returning" in v2.0) is what happens when the caller attempts to use the return value. If a function (without side-effects) returns unset, the effect is the same as the caller having referenced an unset variable instead of a function call. (An UnsetError is thrown, or the expression short-circuits to ??, or a parameter is omitted if the ? suffix was used.)

Descolada wrote:Yet a := b := unset is not an error, nor is a := (b := unset). A bug?
No, these are valid unset expressions as explained under Unset.
  • unset is an unset expression.
  • b := unset is an unset expression because it is "A direct assignment with := or ??=, where the right-hand side is an unset expression."
  • a := b := unset is an unset expression because it is a direct assignment to a where the right-hand side is b := unset, which is an unset expression as per the previous point.
a := b := unset is supported naturally by the recursive nature of the syntax, but also by intent and for the same reason that chained assignments are supported at all (presumably to eliminate the reptition of b := value, a := value).

Descolada wrote:Unsetting an object property does not delete the property itself, but instead is passed into the setter method.
Unsetting a value property does delete the property itself.

Setting a dynamic property does literally nothing except for what you instruct it to do in the setter. If you want the property to be deleted, you must delete it. Of course, you probably don't really want to do that when the property is defined in the prototype, since that's the only place you could delete it from, and that would affect all instances.

Descolada wrote:Is it possible to check whether an object property is "set" without using try..catch?
What does it mean for the b property to "be set"? A dynamic property doesn't have state; it is just a pair of methods that you can call. In this case, it is obvious that the value is stored in _b, so you can check a.HasProp("_b"). But suppose you only have a public interface like this:

Code: Select all

Class a {
    static b {
        get => (some value or unset)
        set
    }
}
All you can see is that a has a property b which you can get or set, and if you get it, there might be no value. All you can do with the property is get or set it, so the question isn't "is the property set?", but more like "if I get the property, will it return a value?". You can only get or set, not look into the future, so it becomes "get the property; did it have a value?". This is no different to allowing for any other potentially unset expression.

In general, the ?? and ? operators are the preferred constructs for dealing with unset. There are basically two ways to evaluate a potentially unset subexpression to a boolean value indicating whether a value was produced:
  • Use ?? with a unique default value. This is possible even if you don't know what values the LHS might have. For instance, unique := {}, has_value := (LHS ?? unique) != unique.
  • Use ? with a function which will return 0 if the parameter is unset or 1 otherwise. Knowing that IsSet does what we want but requires a variable (and isn't a function), a direct and conventional solution would be IsValue(p?) => IsSet(p), which can be called like IsValue(a.b?). [LHS?].Has(1) is creative, but less efficient.
IsSet might permit any unset expression in future, but knowing that it currently requires a variable, you can just give it a variable: IsSet(_:=a.b?)

Qriist
Posts: 161
Joined: 11 Sep 2016, 04:02

Re: Nothing.

Post by Qriist » 14 Dec 2024, 07:34

Descolada wrote:I don't think I can see the value (no pun intended) of returning unset from a function.
lexikos wrote:
13 Dec 2024, 21:07
Variables defaulting to unset instead of "" improves error detection. Attempting to use a nonexistent value causes an error to be thrown, whether the value was supposed to come from a variable or from a function call. Functions don't default to returning unset yet, but one can do it explicitly for similar reasons.
The addition of unset has become a godsend when working with databases because, despite lexikos' stated intent to differentiate, the keyword very much acts like a null value.

EDIT: another big use I have for unset is for conditionally setting default values, as in this method:

Code: Select all

    ReadyAsync(inEasyHandles?,multi_handle?){    ;Add any number of easy_handles to the multi pool. Accepts integers or object.
        inEasyHandles ??= this.easyHandleMap[0][-1] ;defaults to the last created easy_handle
        multi_handle ??= this.multiHandleMap[0][-1] ;defaults to the last created multi_handle

        If (Type(inEasyHandles) = "Integer")
            inEasyHandles := [inEasyHandles]
        for k,v in (Type(inEasyHandles)!="Object"?inEasyHandles:inEasyHandles.OwnProps()) { ;itemize Objects if required
            this.AddEasyToMulti(v,multi_handle)
        }
        
    }
Last edited by Qriist on 15 Dec 2024, 12:02, edited 2 times in total.

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

Re: Nothing.

Post by iseahound » 14 Dec 2024, 19:53

Haven't been able to test with the default return of unset due to bugs which I don't understand: viewtopic.php?f=14&t=131019

Qriist
Posts: 161
Joined: 11 Sep 2016, 04:02

Re: Nothing.

Post by Qriist » 15 Dec 2024, 13:07

iseahound wrote:
14 Dec 2024, 19:53
Haven't been able to test with the default return of unset due to bugs which I don't understand: viewtopic.php?f=14&t=131019
Answered over there, but they seem to boil down to syntax checks that were missed when unset was added. Might be easy fixes?


Cebolla wrote:
12 Sep 2024, 01:53
In this above example, I am incapable of defining a value as `unset` in my template object, because that causes the property to be lost at runtime, so I will no longer be able to use my template object for one of its purposes. So in this case, `unset` does not provide for me a means of accomplishing all required tasks:
1. Exposing to the reader the possible optional input parameters
2. Allowing optional input parameters to be passed as key:value pairs in an object, instead of positional arguments
3. Preparing an effective way to validate input parameters
4. Correctly identifying an optional input parameter as not having a value unless the user/developer defines the value or a value is defined during script executing.
So, I've been chewing on this and I think I came up with something weird but workable: build your own Null type by providing a Null class.

As explained by the docs, when you call Type() on some entity you're actually checking a class value, whether it's proper class or not. For example:

Code: Select all

randomEntity := 1
If (Type(randomEntity) = randomEntity.__Class)
&& (randomEntity.__Class = "Integer")
    msgbox "The type is Integer."
The .__Class property is read only and I don't see a way to change that flag on an existing variable. But, you effectively create new types every time you declare a class, sooooo just make one named Null:

Code: Select all

Class Null {
    ;no really, literally an empty class
}
From there you can instantiate it the same way you would a Map(). And, because it has a "real" value, it won't disappear on you the way an unset value does. You can actually process things with it:
Spoiler
I think this is probably the best way to handle Nulls in AHKv2, barring lexikos making a reserved keyword that does the same.


EDIT: https://github.com/Qriist/Null

lol

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

Re: Nothing.

Post by iseahound » 16 Dec 2024, 01:14

@Qriist You should be able to set a default value in the function signature. So there's no need for ? or ??= as the function reference will persist as a default value. I for get sometimes as well, mostly due to Python's strange behavior.

The null class is very clever!

Qriist
Posts: 161
Joined: 11 Sep 2016, 04:02

Re: Nothing.

Post by Qriist » 16 Dec 2024, 05:48

iseahound wrote:
16 Dec 2024, 01:14
@Qriist You should be able to set a default value in the function signature. So there's no need for ? or ??= as the function reference will persist as a default value.
You're absolutely right given my code's current state. What you see at the moment is a relic of an earlier design where the default was derived in a more complex manner. AHK didn't like that earlier method as a function default so I settled on the ??= checks. When I redesigned the layout to fix a different issue I didn't bother changing the function signatures. I might do so when the class is closer to completion. :)
iseahound wrote:
16 Dec 2024, 01:14
The null class is very clever!
Thank you! I'm glad you think so. :D

Post Reply

Return to “AutoHotkey Development”