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
[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.
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
Code: Select all
int? count = customers?[0].Orders?.Count();