Skip to content

Null conditional operators for coalescing, assignment and member access #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 19, 2020
140 changes: 140 additions & 0 deletions 1-Draft/RFCNNN-Null-conditional-operators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
RFC:
Author: Aditya Patwardhan
Status: Draft
SupercededBy:
Version: 0.1
Area: Language
Comments Due: 09/30/2019
Plan to implement: Yes
---

# Null conditional operators for coalescing, assignment and member access

The RFC proposes to introduce new operators for null coalescing, null conditional assignment and null conditional member access.
The operators provide the script authors a short-hand for performing null checks.

## Motivation

As a PowerShell script author,
I can use null conditional operators,
so that null condition checks are more succinct.

## User Experience

### Null assignment operator - `?=`

Assign the value to the variable if the variable value is null or the variable does not exist.

```powershell
$x ?= 'new value'
${x}?='new value'

$x? ?= 'new value'
${x?}?='new value'
```

### Null coalescing operator - `??`

If the left hand side is null then return the right hand side, else return the right hand side.

```powershell
$x = $null
$x ?? 100
100
```

```powershell
$x = 'some value'
$x ?? 100
some value
```

```powershell
$x? = 'some value'
${x?}??100
some value
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another example?
$x = $x ??= 5
if $x is null before execution, the value of $x will be 5
if $x is not null before execution, the value of $x will be unchanged

Copy link
Contributor

@vexx32 vexx32 Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 wouldn't that leave $x as $null, since $x ??= 5 has no output to pass back to $x in the final assignment? Or is the precedence the same as = and all assignments are "simultaneous" just like $a = $b = 2 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect right-to-left execution, so $x = ($x ??= 5). That's what the compiler does currently for all assignment.

So you can do this:

$x = @(1)
$w = $z, y = $x += 2,3


### Null conditional member access operators - `?.` and `?[]`

Null member access operators can used on scalar types and array types.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should state "can be used" instead of "can used"

Return the value of the accessed member if the variable is not null.
If the value of the variable is null, then return null.

```powershell
$x = $null
${x}?.property
${x?}?.property

${x}?[0]
${x?}?[0]
```

## Specification
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see a lot more examples (likely a bunch of your tests) which really poke the precedence and results.

if ( $x ??= 5 )
if ( $x = $x ??= 5 )
if ( $x ??= $null )
for($x ??= 4; $x -lt 6; $x++)

etc.


The RFC proposes four new operators, the null assignment operator `?=`, the null coalescing operator `??`, and the two null conditional member access `?.` and `?[]`.

### Null assignment operator - `?=`

The `?=` operator check the left hand side to be null or undefined and assigns the value of right hand side if null, else the value of left hand side is not changed.

```powershell
$todaysDate ?= (Get-Date)
```

The value of `Get-Date` is assigned to `$todaysDate` if it is null or undefined, if not null, the value is not changed.

### Null coalescing operator - `??`

The `??` operator checks the left hand side to be null or undefined and returns the value of right hand side if null, else return the value of left hand side.

```powershell
(Get-Module Pester -ListAvailable) ?? (Install-Module Pester)
```

If the module `Pester` is not installed, then install the module.

### Null conditional member access operators - `?.` and `?[]`

The `?.` and `?[]` operators access the specified member if the value of the variable is not null.
If it is null, then null is returned.

```powershell
${x}?.propname
${x}?[0]
```

The property `propname` is accessed and it's value is returned only if `$x` is not null.
Similarly, the indexer is used only if `$x` is not null.
If `$x` is null, then null is returned.

### Additional considerations

The `?=` and `??` operators are binary operators and check for the value of the left hand side to be null or an undefined variable.
They can be used with a space between the variable name and the to operator.
If the left hand side is null, then `?=` assigns the right hand side to the variable while, the `??` operator returns the right hand side.
When they are used without a space between variable name and the operator, a `{}` must be used around the variable name.

The `?.` and `?[]` operators are member access operators and do not allow a space in between the variable name and the operator.

PowerShell allows `?` as part of the variable name.
Hence disambiguation is required when the operators are used without a space between the variable name and the operator.
To disambiguate, the variables must use `{}` around the variable name like: `${x?}?.propertyName` or `${y}?[0]`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading between the lines for the alternate proposal comments, it seems like the final decision is to always consider ? as part of the variable name?

I think that's probably going to make for less usage of these operators in general, given that any use of them is going to complicate syntax an additional level here... What is the reason for rejecting the proposal to preferentially tokenize ? as an operator token? It would simplify syntax in the vast majority of cases, and only require awkward syntax when an already awkward name is in use.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me reading between the lines: I think there is concern that if ? is in use in variable names, and if the suspicion that users who use such names are not advanced users is accurate, preferential tokenizing of an operator like ?. could be very, very confusing to those users. Data needs to be gathered though to really assess that impact.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah I guess I can see that, potentially. That's one of the reasons I'm in favour of ? simply being removed as a standard variable name character, but that has its own problems.

Really, it seems like every solution here is kind of problematic in its own way, but I feel that the solution currently preferred by this RFC is the most problematic, because it creates a pattern for requiring more complex syntax that may be permanent. A one-time break is one thing, but requiring more complicated syntax in (probably) every future version of PowerShell seems like a worse change to me. 😕

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I feel that the solution currently preferred by this RFC is the most problematic, because it creates a pattern for requiring more complex syntax that may be permanent.

Agreed. I think that loses all familiarity that the syntax of ?. would buy. No one is going to think to try that, most don't even know about the ${} syntax in the first place. I'd personally rather see that aspect of this RFC tabled until a suitable syntax is found.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very confused by this statement: If the left hand side is null then return the right hand side, else return the right hand side. (Wouldn't that mean "always return the right-hand side"?)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of operators: -match, -replace, -contains, -split, -join, -as, -is, -isnot, -f, +=, -=, *+, /=, ++, --, etc. etc. The arithmetic operators very C-family based; the others - not so much. Some supporting very odd syntax already (I'm looking at replace and join as I write that).

If someone is willing to looking at something more verbose, then something like:

$varThatMayBeNull -isNull $FallBackValue

seems fairly obvious. Similarly,

$varThatMayHaveNullProperty -isPropertyNull $PropertyName, $FallBackValue

also seems fairly obvious (with something shorter -ispropnull also reasonably verbose).

I currently handle the first with an infix if statement, e.g.:

if( $varThatMayBeNull ) { $varThatMayBeNull } else { $FallBackValue }

The second is more complex, which I would love to replace:

function SafeGetProp
{
    Param
    (
        [Object] $obj,
        [String] $propName
    )

    ## $prop = if( Get-Member -Name $propName -InputObject $obj -MemberType Property ) { $obj.$propName } else { $null }
    ## faster:
    $prop = &{ Set-StrictMode -Off; $obj.$propName }
    $prop
}

$prop = SafeGetProp $varThatMayHaveNullProperty $propertyName
if( $null -eq $prop ) { $prop = $FallBackValue }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-isNull sounds like it's expected to return a boolean value; I'd probably avoid that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-isValueNull. It's flexible. :-) I'm throwing something out there that feels more like PowerShell to me. And is actually readable by average humans.

Copy link
Contributor

@vexx32 vexx32 Sep 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-is<anything> sounds like it's expected to give a boolean result. For example: -isNull $null I would expect to get $true, and I don't think that'd be a particularly uncommon pitfall of logic. 🙂

But I think that is more in the right direction, yeah. I rather like $value -else $fallback just because it kind of has the right feel to it (still far from perfect, but I don't think that we'll find a 'perfect' solution, really), and also because it can tie in with $bool -then $trueValue -else $falseValue -- though it's perfectly possible that may not be desirable.

Copy link
Contributor

@KirkMunro KirkMunro Sep 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SteveL-MSFT To respond to your question asking if anyone has other syntax suggestions that wouldn't result in breaking changes, I was tinkering a little today, and wondered what if we look at ! as a postfix operator that is similar to the dot-reference operator, but only evaluating the right-hand side if the left-hand side is not null?

As far as I can tell, these are non-breaking changes:

# This references the Length member, but only if $x -ne $null
# (no . required, since there's already one at the bottom of the !, but
# we could use !. here if we really felt it was necessary)
$x!Length

# This references the item at index 2, but only if $x -ne $null
$x![2]

That would be non-breaking. I also like the visual relationship between the dot-reference operator (.) and the non-null dot-reference operator (!). The actual operators here would be ! and ![].

When comparing this with the other C# operators (ternary, null-coalescing, etc.), there's no conflict in thought and they are all explainable:

  • ?: is used for shorthand conditionals (logical expressions)
  • ?? and ??= both show that ?? is used to invoke an expression if a value is null
  • ! is used to invoke a member or to retrieve a collection item if a value is not null.

There is one overlap with C#, which has what is referred to in online discussions as the damnit operator (a postfix !), but that's used by the compiler to ignore when a nullable reference type may be null. Since we're interpreted and not compiled, I don't see an issue there.

Also just to make it clear, I prefer sticking with the ?. and ?[] syntax that is already used in C#. But with the committee resisting that breaking change, and since you requested other syntax suggestions, I wanted to share one.


## Alternate Proposals and Considerations

### Disallow `?` in variable names

It was considered to disallow `?` in variable names, to avoid confusion whether the operator is a null conditional operator or the variable name has `?` in it.
This proposal was rejected to maintain backward compatibility.

### Prefer new operators when tokenizing variable names

It was considered to look ahead when tokenizing the variable names and check for `?=`, `??`, `?.` or `?[]`.
If the token is found the assume that it is a null conditional check and not a member access of a variable name ending in `?`.
To maintain backward compatibility and this proposal was rejected.

### Use a different sigil for null-conditional operators

It was determined that the familiarity of `?.` and `??` operators to `C#` programmers will make usage of the new operators easier to understand than introducing new sigils.