Nothing.

Discuss the future of the AutoHotkey language
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Nothing.

Post by lexikos » 06 Sep 2018, 04:08

Flipeador recently brought up the possibility of adding a null type to AutoHotkey. It has been brought up before, but as far as I can tell, never in its own topic. So I wrote down my thoughts on the subject, and this is the result (converted from Markdown with MarkdownToBBCode.ahk).


Null in the Language

null can be used by the language to signal the script of the following:
  • Unset variable
  • Non-existent associative array element (includes data properties)
  • No return value
Currently "" is used for these, ambiguously. Using null instead allows one to detect the presence or absence of a value even if that value is "". However, it really just shifts the ambiguity from "" to null: since null has other uses, it is likely that one would still want a null value in a variable or array, or as an explicit return value.
[Update on 2022-07-02: Attempting to read an unset variable, non-existent property, array element or map element causes an error to be thrown, so the ambiguity is avoided. Functions still return "" by default.]

Missing Parameter

null could be used to indicate at the language level that no parameter value is being supplied. The language would either replace it with the parameter's default value or throw an error.

This is different to Default Parameter (below); here, the null value would typically not be seen by the library function at all.

There are two problems with this:
  • null is given other meanings, so may be needed as an actual parameter value. If null is replaced with the default value, it would not be possible to specify null unless null itself is the default value. This is error-prone.
  • If null is the default value for variables and non-existent array elements/properties, an unintentionally null parameter is far more likely to happen and is harder to debug if null is replaced with the default value. #Warn UseUnset helps, but isn't always enabled and only works with variables.
Alternative to Null

Suppose that null is split into none and unset.

none is given meaning by the script or library, perhaps on a case by case basis. It is safe for the script to use none in any way it wishes, because the language never generates a none value automatically or treats it as a special case.

unset is used by the language as a signal, and might be treated as a special case when passed to a function or assigned to an array element. It is used by the script only for these purposes.

The current implementation of #Warn UseUnset relies on the variable being tagged as uninitialized independently of its default value. A simpler but more broadly applicable implementation could warn whenever an unset value is coerced to another type (if such coercion is allowed) and perhaps under other conditions.

Missing Parameter Revisited

If unset is automatically replaced with a parameter's default value, it should be considered an error to pass unset to a mandatory parameter. This ensures consistency between mandatory and optional parameters, and makes fn(, bar) equivalent to fn(unset, bar).

Given an array args of length 3, fn(args*) and fn(args[1], args[2], args[3]) would be equivalent even if some elements are unset. The array implementation is not required to support sparse arrays, but could do so transparently: whether space has been reserved for it or not, any element which has not been set returns unset.

A function call can optionally provide a parameter value or not depending on some condition or prior calculations, without performing a variadic call:

Code: Select all

; Ternary unset method.
fn(passFoo ? getFoo() : unset, bar)

Code: Select all

; Implicit unset method (#Warn-incompatible).
if passFoo
    foo := getFoo()
fn(foo, bar)

Code: Select all

; Variadic method.
args := [, bar]
if passFoo
    args[1] := getFoo()
fn(args*)

Code: Select all

; Redundant ternary method.
(passFoo) ? fn(foo, bar) : fn(, bar)


Even if unset came from an uninitialized variable, non-existent array element or function call with no return value, passing it to a mandatory parameter always raises an error, while passing it to an optional parameter never raises an error. The latter case could be detected by #Warn UseUnset; however, that only applies to variables.

In theory, errors should be detected and reported as early as possible. Having unset as both the default value and the missing-parameter value makes particular errors hard to distinguish from legitimate cases. Suppose that there is instead unset, missing and none.

missing would be the signal to use the parameter's default value. This would always be explicit, either by the script referring to the literal missing at some point (perhaps storing it in an array or variable) or by writing a comma without an expression, as in fn(, bar) and [, bar].

Naming it usedefault might better clarify the difference between it and unset, but since arrays can be used for anything, [, bar] being equivalent to [usedefault, bar] wouldn't make a lot of sense. On the other hand, the explicit usedefault could be required when constructing a parameter array, or variadic calls could continue to treat absent or unset values as missing parameters. In the latter case, usedefault would add flexibility without actually changing any existing behaviour (so it could be outside the scope of v2).


Null in the Library

No Object

Where a value (parameter or property) can be either an object or nothing, null is a natural choice. Currently "" is used.

Examples: obj.base, Gui.MenuBar, MenuFromHandle, GuiFromHwnd, GuiCtrlFromHwnd, Func.

No Value

There is no value, because one could not be retrieved or because the operation was not applicable. Currently "" is used.

Examples: GuiCtrl.Value when the control type has no value, obj.GetCapacity(x) when obj[x] is not a string, ControlGetFocus when there is no focused control.

Errors

[2020-06-06: ErrorLevel was replaced with exceptions (or alternatives, for non-error situations) in v2.0-a110.]
null may also be returned when an error occurs and the function sets ErrorLevel; this is typically when a window/control/file does not exist or an external API call failed. For functions that normally return a value but return null on failure, the return value can be checked for success instead of ErrorLevel. This is also true for "", except that it is sometimes ambiguous.

Parameter Default

Use: a single variable that either contains a parameter value or not.

When the presence or absence of a parameter affects the behaviour of a function but the parameter has no default value (passing any value at all has a different effect), null can be used to invoke default behaviour.

Currently blank variables have this effect for some functions, but sometimes one has to pass the default value explicitly or completely omit the parameter (which requires lengthier code or a variadic call if the parameter is sometimes defined and sometimes not).

For example, StrReplace/RegExReplace can use null instead of -1 to mean "no limit". It may even read intuitively if the value is called none rather than null.

The difference between this and Missing Parameter above is that it would be coded into each function.

COM/.NET Interop

null can convert to ComObj(VT_UNKNOWN, 0), which is needed often when invoking C# methods.


Related

Operators

A new operator can be added to provide a default value when a value is null. C# calls this the null coalescing operator.

Code: Select all

x := x ?? y
C# also has null conditional operators, which produce null if the target object is null:

Code: Select all

int? count = customers?[0].Orders?.Count();
This seems a bit more challenging to parse due to ambiguity with ternary, unless the sequences ?[ and ?. are reserved for this (so a ? [ b ] : b and a ? .1 : .2 require spacing or parentheses).
Last edited by lexikos on 01 Jul 2022, 21:08, edited 2 times in total.
Reason: update about unset variables etc.

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Nothing.

Post by Helgef » 06 Sep 2018, 13:56

Hello, thank you for the interesting post :wave:.
For non-existent array elements, most often, an exception would be very helpful, it is very similar to call to non-existent method. What would null . 'str' mean? Is it an error? Eg, myUnsetVariable .= 'str'. I assume math ops would throw at least.

In many functions you call via dllcall, you can pass or get back NULL (0), I do not think that could work well with a built-in null value, so I do not like using the name null for that reason.
Alternative to Null
none and unset, I like this better. ; Ternary unset method. and limit := none are good examples. However,

Code: Select all

var := a.nonExistentElement 
f var ;  minparams is 1
doesn't look like it's unset, this looks unset:

Code: Select all

f var
In the first example, the problem comes from the array, and should be detected there.
null may also be returned when an error occurs and the function sets ErrorLevel;
I think exceptions are much more appropriate when errors occur.
There is no value, because one could not be retrieved or because the operation was not applicable. Currently "" is used.
Examples: GuiCtrl.Value when the control type has no value, obj.GetCapacity(x) when obj[x] is not a string
These also looks like errors. guicontrolfocus should not be an error, but we do not need a none or null value, we just need that 0 == "0" is false ;). Otherwise, a none value is fine :thumbup: .

Cheers.

toralf
Posts: 868
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Nothing.

Post by toralf » 06 Sep 2018, 15:36

Just my two cents :
I never had the need for a null so far.
Do I understand it right that “If 0”, “If False” and “If Null” would behave identical?
ciao
toralf

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

Re: Nothing.

Post by lexikos » 06 Sep 2018, 17:17

Helgef wrote:For non-existent array elements, most often, an exception would be very helpful, it is very similar to call to non-existent method.
In some cases or to some authors it would be very helpful. For others it would be very annoying. It would be on par with being unable to if (var != unset) (or as it is now, if (var != "")) when var is unset. I don't use #Warn UseUnset because it complains too much.

It is dissimilar to a call to a non-existent method because methods are virtually always defined in a class, for the entire lifetime of the object, whereas array elements and even properties are often not. It is also dissimilar because retrieval is paired with assignment, and assignment generally always works regardless of whether the element existed (putting aside built-in objects).

Having the unset value allows a convenient way to both retrieve the value of an array element or property and determine its existence at the same time. Having to call a method like HasKey() first is inconvenient and inefficient.
What would null . 'str' mean?
I am of two minds about it. Concatenation is often used to produce a string for debugging. For such cases, producing "nullstr" would be useful. However, treating it as an error might be more useful for error-detection (preempting the debugging). An explicit conversion or the null-coalescing operator can be used if needed.
Is it an error? Eg, myUnsetVariable .= 'str'. I assume math ops would throw at least.
I would be inclined to treat it as an error. The other option is for .= specifically to treat unset as "", since a result of "unsetstr" is not generally useful.

However,

Code: Select all

var := a.nonExistentElement 
f var ;  minparams is 1
doesn't look like it's unset, this looks unset:

Code: Select all

f var
Does it? It looks to me like a function call passing a variable defined elsewhere in the script. Or conversely, the first case looks like an attempt to access an element of the unset variable a.
In the first example, the problem comes from the array, and should be detected there.
What problem? I think you're supposing that unset be replaced with the parameter's default value or throw an error. If that's the case, copying an unset value from one place to another for this purpose is legitimate. It only becomes an error when it is known that the value is being passed to a mandatory parameter. This is the problem with using unset for both purposes.

Making a.nonEx an error (when a is a valid extensible object) breaks the symmetry between a.nonEx and a.nonEx := value, unless the latter is also an error, in which case we need some new (probably more cumbersome) way to define new elements even though variables are defined with simple assignment (or defined by simply referencing them and given value by assignment).

Making a.nonEx an error means that if one wants to reference a property that might not exist (because the source of the object isn't known, or because the property isn't needed for every instance or all of the time, or for any other reason), one has to use a more verbose method call to check its existence first. I think that this type of strictness is not playing to the strengths of the dynamic object type.
I think exceptions are much more appropriate when errors occur.
That is a separate topic which I prefer not to discuss here.
These also looks like errors.
These are just examples based on what currently uses the _f_return_empty and _o_return_empty macros in the source code.

The premise of GetCapacity not raising an error is that it is expected to be used to determine the type of the field. It is not an "exceptional" case. It is a moot point though, because I want to remove these methods completely and replace them with a separate binary object.
guicontrolfocus should not be an error, but we do not need a none or null value, we just need that 0 == "0" is false
I don't understand. Firstly, I assume you mean ControlGetFocus() when there is no focus. "There is no focus" is a perfectly valid answer from the function, so of course it shouldn't be an error. The function currently returns a ClassNN as a string, so I do not see how 0 or "0" enters into it. As it already returns a string, returning an empty string when there is no focus is more appropriate than returning 0. If it returned none when there is no focus, every string return value would be a valid ClassNN.

If ControlGetFocus returns a HWND like Flipeador wants, it is appropriate to return 0 when there is no focus. There is no ambiguity.

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Nothing.

Post by kczx3 » 06 Sep 2018, 19:20

Was this one of your main goals for v2? I’m not sure as to the amount of development it would take to implement any of your suggestions. However, it doesn’t seem like this is a feature which would keep v2 from becoming generally released (or even in beta for that matter). While I think that having a null type (and I do prefer just the single null, not none and unset), I feel that time would be potentially better spent towards other things you want/need done for v2 to get out of beta. That said, I’m happy you’re finding things to keep motivation on the project and don’t want to hinder that.

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

Re: Nothing.

Post by lexikos » 06 Sep 2018, 19:22

No, this is not part of any plan. It is just a bunch of thoughts and hypotheticals that I wrote down in response to someone indirectly requesting null, as I wrote at the top.

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Nothing.

Post by kczx3 » 06 Sep 2018, 19:25

Good enough :thumbup:

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

Re: Nothing.

Post by lexikos » 06 Sep 2018, 21:31

toralf wrote:Do I understand it right that “If 0”, “If False” and “If Null” would behave identical?
That would be my preference.

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Nothing.

Post by Helgef » 07 Sep 2018, 12:54

I am of two minds about it.
I don't use #Warn UseUnset because it complains too much.
Then I do not see any reason to have variables default to unset, just keep them as empty strings, and array elements too. Let unset be only for omitting a parameter. Then we keep the convenience of uninitialised variables and array elements acting like empty strings. And we get a simple and convenient way of omitting parameters, as you exemplified. Further, array.key := unset and var := unset has a clear meaning, and especially for arrays, there is a clear difference from array.delete('key'). In a literal array definition, omitting a value could be equivalent of specifying unset, eg, [,a] is the same as [unset, a]. There will be no question about what obj[unset] := ... means. When on the topic of omitting parameters, I think it would be valuable for UDFs to be able to have optional parameters without default values, eg, f(opt param1) { then one can check if param1 == unset, which will be true if you call either f() or f([var:=]unset). Passing unset to a parameter with a default value, sets the default value ofc. All unrelated use of unset should cause an exception, eg, unset . 'str'.

Finally, add a unique, falsy, none value with type none, for use when eg '' or 0 is ambiguous, or whatever else one might want to use it for.
It is dissimilar to a call to a non-existent method because methods are virtually always defined in a class, for the entire lifetime of the object, whereas array elements and even properties are often not.
Having the unset value allows a convenient way to both retrieve the value of an array element or property and determine its existence at the same time.
I disagree. Especially for properties, a property get function might very well return something which is unset (perhaps by mistake, or perhaps not), it doesn't tell you anything about the existence of the property, an error does. We do not need unset for this, just an error.
Does it? It looks to me like a function call passing a variable defined elsewhere in the script. Or conversely, the first case looks like an attempt to access an element of the unset variable a.
I meant,

Code: Select all

a := []
var := a.37
f var
f(p){
}

Code: Select all

; the entire script
f var
f(p){
}

Making a.nonEx an error (when a is a valid extensible object) breaks the symmetry between a.nonEx and a.nonEx := value, unless the latter is also an error, in which case we need some new (probably more cumbersome) way to define new elements even though variables are defined with simple assignment (or defined by simply referencing them and given value by assignment).
I do not see any value in that symmetry. The latter doesn't need to be an error, when you make a mistake setting, you will be notified when you try to get it back (enumerating aside). I think the choice to put the blame on the getting, not the setting, is valid, just as it is done with #warn for uninitialised variables.
I think that this type of strictness is not playing to the strengths of the dynamic object type.
I do not think it is about strictness, it is about being less sloppy, and being notified about common mistakes. Mistakes that are more common than this, where I think it is perfectly reasonable to verify that properties do exist,
the source of the object isn't known, or because the property isn't needed for every instance or all of the time

Firstly, I assume you mean ControlGetFocus() when there is no focus. "There is no focus" is a perfectly valid answer from the function, so of course it shouldn't be an error.
Yes.
The function currently returns a ClassNN as a string, so I do not see how 0 or "0" enters into it. As it already returns a string, returning an empty string when there is no focus is more appropriate than returning 0. If it returned none when there is no focus, every string return value would be a valid ClassNN.
I thought that '' was ambiguous. Maybe it isn't, so then '' is fine. But when an empty string is ambiguous for a function which returns strings, then returning a pure 0 could act as none/ null if the compairson 0 == '0' was false. You could also do an additional call to type ofc, but that is inconvenient.

Cheers.

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

Re: Nothing.

Post by lexikos » 07 Sep 2018, 17:52

Helgef wrote:Let unset be only for omitting a parameter. Then we keep the convenience of uninitialised variables and array elements acting like empty strings. And we get a simple and convenient way of omitting parameters, as you exemplified.
The name was based on it being the default value for variables which haven't been set, like #Warn UseUnset. Considering the "omitting a parameter" value in isolation, I am inclined to leave it as it is (implied by an omitted parameter and never actually exposed to the script), otherwise I would name it missing or usedefault as I mentioned.

It's not really clear to me how a default value of "" is convenient, though I assumed it was. The only case I could think of was for building a string with .=, but in those cases I always initialize the variable first anyway. A default value of "" can be an inconvenience when one wants the value but also wants to distinguish between "uninitialized/absent" and "zero length string", such as for initializing a static or global variable on first use.

We already have a simpler and more convenient way of omitting parameters; i.e. actually omit them. An explicit "this parameter is omitted" value gives us an easy way to conditionally omit a single parameter, but this is not something I would use often.
Further, array.key := unset and var := unset has a clear meaning,
I don't think so at all, but maybe that's because you're using "unset" as "omit this parameter" outside the immediate context of a function call.
There will be no question about what obj[unset] := ... means.
What does it mean? (See, you were wrong.) unset may be a value, but in this case you are implicitly making a function call. Are you intending to omit the index, or use unset as a key?
I think it would be valuable for UDFs to be able to have optional parameters without default values, eg, f(opt param1) { then one can check if param1 == unset,
That would be f(param1:=unset){.
All unrelated use of unset should cause an exception, eg, unset . 'str'.
This makes it harder to print the contents of a variable for debugging.

I forget the original reason now, but I had considered just how much could be considered an error, and how the error-detection capabilities would weigh up against (in)convenience. For instance, you say that if (obj.prop = unset) doesn't tell us whether the property exists because the property could return unset, but that wouldn't be true if properties are not permitted to return unset. In the end I mostly concluded that a language should be either "sloppy", providing a value that usually means the value or property wasn't defined, or "strict", treating every attempt to access an uninitialized variable or property as an error and never defaulting to any particular value.

By the way, there are only minor differences between "my" idea of unset/none and JavaScript's undefined/null. JavaScript is the reason I was aware of the problem with conflating the "unset variable" value and the "omit this parameter" value.
I disagree. Especially for properties, a property get function might very well return something which is unset (perhaps by mistake, or perhaps not), it doesn't tell you anything about the existence of the property, an error does.
I did not mean to imply that it could be used without ambiguity with every object, when everything about the object is unknown. In the more limited context of an object expected to have a given property at some point during its lifetime but not its whole life, or an array known to contain certain types of values, unset is not ambiguous. Even in other cases, unset provides more clarity than "", which is already used in comparisons that way sometimes.
I do not think it is about strictness, it is about being less sloppy,
You say tomato...

As an aside, if all objects are given a common base, you will have the flexibility to enable this type of "less sloppiness" in your own scripts if you wish. You can already do it for your own classes. Actually, you can already do it for {} and [] as well, but it gets messy.
But when an empty string is ambiguous for a function which returns strings, then returning a pure 0 could act as none/ null
I don't believe it makes sense to use 0 for this purpose, even if you disable implicit conversion of integers to strings to prevent one of the errors it would otherwise be likely to cause.

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Nothing.

Post by kczx3 » 07 Sep 2018, 19:45

I use JavaScript a lot so undefined and bull are both very familiar to me. I guess I just like those names better than none/missing/unset.

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Nothing.

Post by Helgef » 08 Sep 2018, 06:21

The name was based on
That is fine, unset as in unset parameter is fine too imo.
It's not really clear to me how a default value of "" is convenient, though I assumed it was. The only case I could think of was for building a string with .=, but in those cases I always initialize the variable first anyway. A default value of "" can be an inconvenience when one wants the value but also wants to distinguish between "uninitialized/absent" and "zero length string", such as for initializing a static or global variable on first use.
I called it convenient because I assumed you thought it was convenient, and because I do think it is convenient when you want the value to be an empty string. But mostly, I think it is inconvenient because of the reaons you stated, but more because most variables (and array elements) has more purpose than being blank strings, and especially doesn't need to start off as such, so it is more reasonable to demand they are explicitly set before use, imo. So we do not need some special value for it, just #warn for unset by default and a.nonEx causing exception. objdelete already exists, some thing like freevar() could be used to mark a variable as uninitialised. I think that is simpler than an explicit null value.
I had considered just how much could be considered an error, and how the error-detection capabilities would weigh up against (in)convenience. [...]
In the end I mostly concluded that a language should be either "sloppy", providing a value that usually means the value or property wasn't defined, or "strict", treating every attempt to access an uninitialized variable or property as an error and never defaulting to any particular value.
I agree, and I'd rather v2 continue to move towards the stricter side.
I don't think so at all
If the unset/missing value has one use, it is clear. That was the point, and of course, one has to have read the documentation.
What does it mean? (See, you were wrong.) unset may be a value, but in this case you are implicitly making a function call. Are you intending to omit the index, or use unset as a key?
If unset has one meaning, omitting the parameter, that is just that, i.e., it is the same as obj[] := .... Whatever that means depends on obj.
That would be f(param1:=unset){
Hehe, yes ofc you are right :thumbup: . I was mixing in an other idea, the idea of detecting missing parameters: f(opt param1){ + isMissing(param1). It makes more sense to ask, is missing, if there is no default value. Imo, this is sufficient, but the idea of a missing value is more elegant, since it works on both sides. However, I think the more important part is that to be able to detect a missing parameter inside the function, default values doesn't suffice for this in general. And as you said, we can just omit a parameter.
This makes it harder to print the contents of a variable for debugging.
Sure, but there could be a separate (set of) debug function(s) for that, although, making something like that, widely pleasing, is a challenge.
I don't believe it makes sense to use 0 for this purpose, even if you disable implicit conversion of integers to strings to prevent one of the errors it would otherwise be likely to cause.
It makes very much sense for just that, there are other cases of course where pure 0 wouldn't suffice.
As an aside, if all objects are given a common base
I do not think that is very interesting for code intended for general scripts, or for scripts which depends on such code. It can be helpful developing general code I suppose. It can also be fun.

Tomato :wave: .

User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Nothing.

Post by nnnik » 08 Sep 2018, 08:50

That is fine, unset as in unset parameter is fine too imo.
I don't think we should make more cascading errors a new feature.
Recommends AHK Studio

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

Re: Nothing.

Post by lexikos » 09 Sep 2018, 00:56

Helgef wrote:If the unset/missing value has one use, it is clear. That was the point, and of course, one has to have read the documentation.
There is no documentation yet, and even clear documentation does not excuse the lack of intuitive clarity. What purpose should array.key := unset and var := unset have? I would guess that you mean to assign the value for later use passing to a parameter, to indicate the parameter should be omitted; but this was not my first guess, since I wasn't thinking in the mode of "unset means missing". In that case, I suppose that at this point unset really has no special purpose within this assignment and is just being assigned like any other value. However, what do you think will happen when array.key := unset invokes a property function or meta-function, with unset as one of the parameters? The purpose is not clear.
If unset has one meaning, omitting the parameter, that is just that
Regardless, it is not as clear as you think. The value has one meaning, but it is still a value. Many users will not recognize that this is the same as a function call, so will not recognize that this value will be treated differently in this context.
It makes more sense to ask, is missing, if there is no default value.
The parameter is a variable. It must have a default value, except in the hypothetical case where a variable can lack a value and attempting to read it will throw an error.

The only parameters that can have no value are parameters that are not formally declared. You need only let the parameter be absent when no value is provided, and then check for its presence. (What I'm getting at is that the problem already has a solution: variadic functions.)

Actually, I suppose that if the parameter is a variable, the idea of a missing parameter translates to uninitialized variable. AutoHotkey provides a default value for uninitialized variables but still allows them to be detected (optionally via #Warn). This capability could be exposed via a function, though it would come with the caveat that either 1) a variable for which a warning MsgBox has been shown is considered initialized, or 2) the warning MsgBox is shown an unlimited number of times for each variable, instead of just once.
Sure, but there could be a separate (set of) debug function(s) for that
For what? Concatenating strings? There are many methods of logging or displaying debug information that depend on first formatting a string containing the values. Format() could permit null values, but it's not always as convenient or readable as concatenation. String interpolation (i.e. syntax sugar for Format()) would help here, but that's very much like concatenation, so allowing null there would introduce inconsistency and might defeat the purpose of having concatenation with null be an error.
It makes very much sense for just that,
I already disagreed, and you haven't provided any new information or arguments...
I do not think that is very interesting for code intended for general scripts, or for scripts which depends on such code.
I guess you see it as a toy that you would not use when sharing scripts, because it would be incompatible with scripts written by others and not part of a standard installation. I (want to) see it as something that allows progress within the language (or standard use of it) without requiring my intervention.

I am truthfully not very interested in making decisions (for other people) about the language, but I am even less interested in using or developing a language based on opinions that I disagree with. Developing a platform for assembling a language is a more appealing idea, but too ambitious, so I just try to add a little flexibility.

User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Nothing.

Post by jeeswg » 09 Sep 2018, 01:34

- I would hope that using 'null' would always be unnecessary, but I'm not opposed to its availability.
- A 'null' flag for variables might be an all-purpose solution.
- 'IsNull' and 'SetNull' functions would be needed.

Code: Select all

obj := {}
obj.key := "value"
MsgBox(obj.key) ;(blank string)
MsgBox(obj.key2) ;(blank string)
MsgBox(IsNull(obj.key)) ;0
MsgBox(IsNull(obj.key2)) ;1
- At the moment the only functionality that I feel is needed is within function definitions, something like:

Code: Select all

A_ThisFuncParams.HasKey(3)
;or:
IsPassed(Param3)
- I saw VT_UNKNOWN mentioned earlier, although not ComObjMissing.
- I had thought that possibly some AHK built-in functions could treat one or both of these as an omitted parameter.

Code: Select all

;VT_ERROR := 0xA ;DISP_E_PARAMNOTFOUND := 0x80020004
m := ComObjMissing()
m := ComObject(0xA, 0x80020004)
;VT_UNKNOWN := 0xD
m := ComObject(0xD, 0)
- This is already possible as a method of last resort:

Code: Select all

oArray := ["abcde", 3]
MsgBox, % SubStr(oArray*)
oArray := ["abcde", 3, 1]
MsgBox, % SubStr(oArray*)
- For reference, here's a list of possible uses for 'null':
function call: omit a parameter
function call: check if a parameter was omitted
function call: a return value indicating failure
arrays: delete a key by assigning a null

- @lexikos: Thanks for the detailed OP.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Nothing.

Post by Helgef » 09 Sep 2018, 03:05

lexikos wrote:... The purpose is not clear.
You are correct, clearly my reasoning failed.
The parameter is a variable. It must have a default value, except in the hypothetical case where a variable can lack a value and attempting to read it will throw an error.
I wonder, do you think the hypothetical case is unreasonable? Variadic functions are mostly fine too, especially if a.nonEx would throw an exception ;).
For what? Concatenating strings?
For printing stuff, for debugging purposes, eg, dbgPrint myObj, myNull, myString where myObj prints as myObj.tostring() or if that method doesn't exist < Object > and myNull prints as < NULL > and myString ...
I already disagreed, and you haven't provided any new information or arguments...
I believe you disagreed first without any new information or arguments...

Cheers.

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

Re: Nothing.

Post by lexikos » 09 Sep 2018, 05:54

Helgef wrote:I wonder, do you think the hypothetical case is unreasonable?
I reasoned about it, therefore it is reasonable. I'm not very interested in considering it and even less interested in discussing it right now.
I believe you disagreed first without any new information or arguments...
I did in fact present new information at that time: at the very least, my opinion had not previously been noted. There was also the implied argument that it would be likely to cause errors due to implicit conversion to string. After that, both our opinions were known. Do you have some argument to make, or are we done here?

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Nothing.

Post by Helgef » 09 Sep 2018, 06:02

I reasoned about it, therefore it is reasonable
That is reasonable.
are we done here?
Clearly.

I apologise for wasting space and time.

Cheers.

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

Re: Nothing.

Post by iseahound » 12 Sep 2018, 19:11

lexikos wrote: No Object

Where a value (parameter or property) can be either an object or nothing, null is a natural choice. Currently "" is used.
This reminds me of the Maybe type or the option type. You're absolutely right in that returning an empty string seems odd in this scenario.

However, how would type checking work? If a function fn(a, b) { return [a,b] } specifies a second parameter, would it be possible to call fn("something", nothing)? The two outcomes are that either the function call throws an error, or b has the value of nothing. I think that adding the possibility of a nothing would force all local parameters within functions to have a default value of nothing because it is likely that function calls are chained, so passing on the value of nothing to a function that sets a default parameter can be possible.

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

Re: Nothing.

Post by lexikos » 12 Sep 2018, 20:43

I don't see what you're getting at. What type checking? Why wouldn't it be possible to call fn("something", nothing)? Are you using nothing as a synonym of unset, meaning missing as already discussed above? I think that the idea of a value that gets replaced with the parameter's default value is fundamentally flawed, and even more error-prone if there's only one null value with this and several other uses.

In statically typed languages, null/Nothing is just a value that a variable of object type can have, or that can be passed to a parameter of object type. Some scripting languages are the same, except that because they aren't statically typed, any variable or parameter can be null/None. It's just a value.

One of the problems with using "" as a default or null value is that if methods or properties are added to the String type (with either language changes or the default meta-function), these might be invoked accidentally when it would be more appropriate to throw a "no object" exception. For instance, myArray.length could be 0 when myArray is "".

Post Reply

Return to “AutoHotkey Development”