PowerShell / PowerShell

PowerShell for every system!
https://microsoft.com/PowerShell
MIT License
43.66k stars 7.08k forks source link

Add support to ternary operator #10161

Closed daxian-dbw closed 4 years ago

daxian-dbw commented 4 years ago

PR Summary

Add support to ternary operator <condition> ? <if-true> : <if-false>. It results in a TernaryExpressionAst.

PR Context

Ternary operator has lower precedence than binary operator, so you can write $a -eq $b ? "Hello World" : [int]::MaxValue.

Implicit line continuance for ? is supported, so you can write

$PSEdition -eq 'Core'
    ? "PowerShell Core"
    : "Windows PowerShell"

Today, 1?2:3 is parsed as a command name as 1?2:3 is considered as a generic token by the tokenizer. We don't want to break that, but in certain situation, we know we are expecting an expression and a generic token like 1?2:3 is not useful. In those cases, we want to make the ternary operator chars ? and : to force ending a number token scan. A new bool field ForceEndNumbeOnTernaryOpChars is added to Tokenizer and used when scanning number tokens. When the tokenizer is current in Expression mode, ForceEndNumbeOnTernaryOpChars is true, and the current char is ? or :, we force ending the number token. With this, we are able to write $a -gt 2?[int]::MaxValue:3, ${true}?3:1, and -not 1?2:3.

Characters ? and : are valid chars for a variable name, so $varName?2:3 will be parsed as a variable. In order to write concise ternary expression with a variable condition, you can do this: ${varName}?2:3.

This PR is a draft, not finished yet

Send out the draft PR to start collecting feedback. Also, exercise the CI builds to find regressions. Pending work:

  1. Finish examining all visitor usages, and update appropriately. Example, VariableAnalysis needs to be updated to account for the ternary operator.
  2. Examine if UpdatePosition is correctly called at the right place.
  3. Add many tests.

PR Checklist

KirkMunro commented 4 years ago

Implicit line continuance for ? is supported

Hooray for implicit line continuance! πŸŽ‰

A new bool field ForceEndNumbeOnTernaryOpChars is added to Tokenizer and used when scanning number tokens. When the tokenizer is current in Expression mode, ForceEndNumbeOnTernaryOpChars is true, and the current char is ? or :, we force ending the number token.

The description of this PR includes a mispelling. The bool field should be ForceEndNumberOnTernaryOpChars.

Also that flag sounds a good candidate for an Optional Feature. Even with that proposal just in RFC, it would be useful/helpful to consider how it could be used for this optional way to parse ternary operators, so that we work out any kinks in the proposed optional feature design.

daxian-dbw commented 4 years ago

The description of this PR includes a mispelling. The bool field should be ForceEndNumberOnTernaryOpChars.

@KirkMunro Thanks for pointing out this. @kvprasoon also caught it. It has been fixed.

KirkMunro commented 4 years ago

@daxian-dbw Does the fact that Where-Object has an alias of ? get in the way of implicit continuance using ? at the start of a line? I don't think it's likely, because you would have to be using -InputObject in that scenario, which means working with a collection in Where-Object which is unlikely; however, something obscure like this is possible:

$c = @(gps -name pwsh)
$b = $StopProcess -eq $true
? -InputObject $c Count -gt 0
| % {if ($b) {spps $c -whatif}}

That's messy, and hopefully nothing like that exists in scripts, but I wanted to call out the potential conflict with the ? alias so that the potential issue is known/documented.

daxian-dbw commented 4 years ago

@KirkMunro Good catch. The continuation of ? will break ? -InputObject $c Count -gt 0 in that case, so it will be a potential breaking change. Personally, I'm not too worried about it. That's a very obscure use of Where-Object and I presume the possibility of actually breaking any scripts is very low. But we do need the committee review to approve this.

KirkMunro commented 4 years ago

@daxian-dbw That's my take as well. I at least wanted to capture it so that if this moves forward with implicit continuance (I hope it does), the obscure break would be known/documented.

Taoquitok commented 4 years ago

With ? already existing as a widely used alias, and Powershell often remaining distinct to common operator designs like ==, >, ||, &&, would it make sense to consider using something more verbose? So following the design of the rest of the powershell in going for verbosity and readability over shortness?
For example, instead of: $a -eq $b ? $a : $c something like -then / -else ?
$a -eq $b -then $a -else $c

Or to save on included -then, but forgot -else errors caused by the above, treat it much like -replace 'a','b'/.replace('a','b'), and have an explicit -ternary a,b / .ternary(a,b)?

($a -eq $b) -ternary $a, $c ($a -eq $b).ternary($a, $c)

Finally having ternaries would be great, and so would improving consistency with other languages, but I would be surprised if two uses for ? didn't cause confusion and readability issues compared to a more verbose, but still short, operator/method.

lzybkr commented 4 years ago

I proposed -then/-else here so you can see the subsequent discussion.

Having spent the past 2 years away from C# and using languages without the cond ? then : else syntax (mostly F# and Rust), I think -then/-else is highly suitable syntax for PowerShell.

I find the arguments for familiarity weak at best - those used to the familiar syntax are likely to get used to the PowerShell syntax quickly, and for those not familiar, I think the readability of a slightly more verbose syntax is really important.

Maybe this PR could enable both forms and keep the feature experimental until folks have some experience with it.

KirkMunro commented 4 years ago

One downside that is significant (to me at least) is that using -then/-else syntax would mean implicit line continuation would be a more risky breaking change, since users could have a command called -then or -else. That may not seem like a big deal, but it will likely be common for users of this feature to want it to span multiple lines (based on this being common in other programming languages), and PowerShell could only support that natively with -then or -else at the end of the line, which is not as readable as it would be with them at the start of a line.

lzybkr commented 4 years ago

It's curious that a hypothetical command -then or -else is more worrisome than an actual command ?.

KirkMunro commented 4 years ago

Well, the actual ? command is an exception in that regard, because:

It's such an obscure scenario, I'm highly skeptical that anyone will be affected by it.

On the other hand, with respect to the potential for -then or -else commands, I have literally no data to work with, so I don't have as high of a confidence level that there wouldn't be a conflict/break somewhere.

WithHolm commented 4 years ago

Just a question: Why?

why would this ever be a good idea for Powershell? what would it add? I mean, I get it. Its somewhat easier to write than a simple "if" and with this you would also get Elvis notation, that could possibly be helpful, but both the language overhead (if you come from a non-programmer background, this makes no sense) and the possibility that this could break the tokenizer if not properly implemented (especially with ? being used for where-object as stated earlier in this thread), i cannot see any way that this would help the language progress in any way.

JamesLear92 commented 4 years ago

@WithHolm Because the ternary operator is one of the most concise and neat abstractions available in many languages. While it could break the tokenizer if not properly implemented, I'd argue that it should be properly implemented.

Taoquitok commented 4 years ago

I'm probably missing something obvious, but are you sure there would be a line continuation issue for the -then -else suggestion? Currently you can do the following with multiple operators over multiple lines:

'string' -replace
    'ing',
    'new_string_ending' -split
        '_'

so presumably a consistent option would be the same format for -then -else?

$a -eq $b -then
    $a -else
    $c

Assuming that this isn't viable, why not -ternary / .ternary() ?

Much like with the current behaviour of -replace / .replace(), which can be spread across lines, using this existing operator format would be consistent with existing behaviours, the below works with replace

('test').Replace(
    'te',
    'be'
)
# output: best
'test' -Replace(
    'te',
    'be'
)
# output: best
'test' -Replace
    'te',
    'be'
# output: best
$a = 'one'
$b = 'two'
$a + $b -replace 
    'et',
    ''
# output: onwo

So why not follow the same pattern for ternary?

# Same line
$a -eq $b -ternary $a,$c
($a -eq $b).ternary($a,$c)

# multi-line
$a -eq $b -ternary 
    $a,
    $c

($a -eq $b).ternary(
    $a,
    $c
)
KirkMunro commented 4 years ago

@WithHolm:

if you come from a non-programmer background, this makes no sense

If you come from a non-programmer background, a lot of PowerShell will initially make no sense. Then you learn how it works, and use what you have learned. This is no different.

the possibility that this could break the tokenizer if not properly implemented

Literally any feature can break something if it is not properly implemented. If our condition for adding something to the language was whether or not it could break something if it was not properly implemented, the language would never change.

(especially with ? being used for where-object as stated earlier in this thread)

As also stated earlier in the thread, there is a very, very low probability that the potential collision with Where-Object would occur because of how that cmdlet is used. It can only collide if the ? alias happens to be used to start a pipeline, which while possible, literally nobody ever does, and that line would also have to be on a line immediately following a conditional expression. The chance that would break something is almost non-existent (and may actually be non-existent).

why would this ever be a good idea for PowerShell? what would it add? i cannot see any way that this would help the language progress in any way.

Ternary operator support has been requested by many PowerShell users, so there seem to be plenty of folks who would use this. I've wished I had ternary operator support many times when it would make my code more elegant and easier to read. It doesn't absolutely have to come in the form of ?: for me, but as someone who works with C#, I do like that syntax a lot.

KirkMunro commented 4 years ago

@Taoquitok:

I'm probably missing something obvious, but are you sure there would be a line continuation issue for the -then -else suggestion?

The line continuation issue potentially comes up when you're using implicit continuance, with the operators at the start of a line.

daxian-dbw commented 4 years ago

3239 has a great discussion about the syntax of the ternary operator. It's definitely worth a read.

essentialexch commented 4 years ago

Since @SteveL-MSFT invited commentary on twitter (I thought this was a "done deal")… overall, I don't see the point. Yes, a ternary operator is more "terse" than the "if(){}else{}" but not much. (And that terseness makes it slightly less readable to a new PS user or any user who is from a non-C language.) And the 'if' statement has been around forever. And there are no concerns about multi-line parsing or operator precedence or operator confusion.

If there is going to be a ternary operator, my personal opinion is that using -then/-else is more PowerShell-y, less confusing to novice users, and eliminates any operator confusion. Recognizing, of course, at that point you've lost most terseness (perhaps all, depending on your style):

Existing: if($b -eq 1) { 1 } else { 0 }

Two -then/-else styles

$b -eq 1 -then { 1 } -else { 0 }
$b -eq 1 -then 1 -else 0

Two ?: styles

$b -eq 1 ? 1 : 0
$b -eq 1 ? { 1 } : { 0 }

Although based on @BrucePay comments in https://github.com/PowerShell/PowerShell/issues/3239#issuecomment-383776193, it seems that some of those '{}' blocks will actually have to be '()' expressions for ternary statements - which I would find extremely confusing.

One more comment, copied from https://github.com/rust-lang/rust/issues/1698#issuecomment-3706301: "I use the ternary operator a lot in C++, but I think the syntax is poor. You have to spot the ? and : in the middle of a sea of other tokens to see that it's even a conditional." Using -then/-else addresses that visibility issues.

rkeithhill commented 4 years ago

If the decision is to go with $b -eq 1 -then 1 -else 0 then it is not worth making the change IMO. The whole point of the ternary operator is to cut the ceremony for a simple concept: $foo = $var ? $var : "default". And FWIW, I'd like to see that ceremony cut further with a null-coalescing operator e.g. $foo = $var ?? "default".

Would folks be happier with Posix param substitution for this scenario e.g.: $foo = ${var:-'default'} (or however that concept would map into PowerShell). I figured $foo = $var ? $var : "default" would be easier to understand and cover one of the more common cases for assignment.

vexx32 commented 4 years ago

Yeah, the point here is the brevity, really. I think the proposed $condition ? $ifTrue : $ifFalse is the best way to go that we currently have on the table. -then and -else do fit in a little better with PowerShell operators, but in terms of terseness they're essentially equivalent to just doing if ($condition) { $ifTrue } else { $ifFalse }.

Having them as distinct, brief symbols, which are very visually distinct (not word-operators, etc) is the value here; they're small, easily distinguished markers in most cases.

daxian-dbw commented 4 years ago

I think it would be easy for scripters to grasp the meaning of $condition ? <true-expr> : <false-expr>. If we go with -then -else for ternary operator, then for the null-coalescing operator, maybe something like -ifnull, but for null-conditional operators, we just have to use the cryptic symbols ?. and ?[], which has the ? in them. Considering that, it's better to be consistent.

essentialexch commented 4 years ago

I think @Jaykul (Joel Bennet, don't know his github name) had a great idea with -then/-else on twitter (https://twitter.com/Jaykul/status/1151913597930496000):

Would the -then -else still have to be used together?

Can -else also be a null-coalescing operator?
$ComputerName = $ComputerName -else "localhost"

Can -then be a conditional set operator?
$ComputerName = $ComputerName -eq "." -then "localhost"

These are very consistent - and very "PowerShell-y" and, in my opinion, are immediately understandable. And eliminates the cryptic characters.

I guess at some point it's about philosophy. PS isn't C or C#. You can move it toward them, which ?: does, or continue with its scripting roots, which tends to be a bit more verbose and more accessible to a non-programmer.

vexx32 commented 4 years ago

While I think such a suggestion is worth considering, I don't think it bears relation to this PR. -then and/or -else could be implemented as binary operators for those cases. In fact, I'd love those ones to be available. But I don't think they should be the same / overlapping with a ternary syntax; would love to talk over some possibilities of those in a separate issue, as I don't think their possible implementation is particularly entwined to this PR itself.

This PR introduces a ternary operator syntax. This structure has never been used in PS before, and it will be (as of this PR, anyway) the only instance of a ternary syntax in PS. As such, I think it makes more sense to break away from (somewhat) traditional -operator syntax; retaining that syntax will most likely lead to more confusion about the ways you can use the operators or not.

I think that while, yes, it's important to have features that are verbose for new scripters, we already have that — if ($condition) { $val1 } else { $val2 } conveys that perfectly well. This would simply be a more brief version of that, just like how we have both Get-ChildItem and gci. Making it more verbose defeats the purpose of the PR, I think. We already have a "more verbose" version available. πŸ™‚

essentialexch commented 4 years ago

@daxian-dbw has already entwined ternary with null-coalescing and null-conditional, in the comment immediately preceding mine. Which is why I mentioned it. I wasn't being hypothetical.

You wrote "retaining that syntax will most likely lead to more confusion about the ways you can use the operators". What's the justification for that statement? The operators do not currently exist, so I don't see how introducing them leads to confusion. Their meaning is far more clear, in my opinion at least, than ?:.

vexx32 commented 4 years ago

I... am not sure what you're referring to, I'm afraid. Both null-coalescing and null-conditional syntaxes have similar but distinct syntaxes in the forms he proposed. With @Jaykul's proposal that you're talking about, the exact same operators would be used in both cases, creating an avenue for confusion.

All -eq style operators currently are binary operators -- they take a left hand and right hand side operand, and they don't take anything else into account.

The proposal here is to implement a syntax that depends on three arguments instead of two, with a short-circuiting behaviour such that the branch that doesn't get followed never gets evaluated, so you don't have to worry about side effects. There are currently zero operators that work this way in PowerShell, so I think that grouping it in with a -then / -else syntax is likely to lead to confusion in usage, with the behaviour unclear when one or both is used, depending how you approach it.

I simply thing that you're trying to cram a bit much in the one box here. Some PS operators have variant behaviours, but that's always due to a difference in their arguments. None of them currently change based on whether you supply another operator. The -eq operator doesn't alter its behaviour if you later use -ne -- this would be a very unusual thing.

I think it makes the most sense to have -then and -else be separate operators, not rolled into the ternary syntax here.

essentialexch commented 4 years ago

I understand what you are saying. The only avenue for confusion I see is specifying -else/-then as opposed to -then/-else. Which would be a syntax error, just as it is if you specified a standalone else today. All of these are interpreted based on where they are used in the language grammar. That doesn't change. ? is location dependent, : is location dependent, etc.

I would point out that operator precedence does directly affect how an expression evaluates, both mathematically and logically. So -eq can have an unexpected result, as I'm sure we've all been surprised the first time we write "-not $a -eq $b".

Regardless, I am actually not particularly invested in this. I wrote my first ternary in 1981. The syntax doesn't confuse me and I don't think it's particularly difficult to understand; but it is terse and can be difficult to read.

I just think PS should stay PS and not try to be a C-family lookalike. I don't think ?: is PowerShell-y at all.

Peace.

Jaykul commented 4 years ago

We've definitely had the debate over whether or not to include this already -- but when examples get non-trivial and start wrapping lines, I'm not sure how a person whose first and only language is PowerShell is supposed to understand what's going on.

They may know ? is Where-Object and they might have learned that :label is used for breaking out of looping constructs, but how will they read this?

$Script:Config.RemoteServer -eq $ParameterValue
    # Describe what this does
    ? (Invoke-Command -Cn $Script:Config.RemoteServer {
           <# do things #> 
    })
    # The parenthesis here are required if it's a command, right?
    : (Do-SomethingElse)

After all, although it doesn't appear to do anything, ? (...) is perfectly valid syntax that actually does roughly the same thing as (...)|out-null right now:

? (iwr google.com -OutFile g.html -PassThru)

I'm not concerned you'll break that -- I'm asking how a user is supposed to know that's not what you're doing.

If you put the ":" on the following line, then suddenly the space or parenthesis after it is mandatory, right? Since otherwise :Whatever is a valid command name? I don't know. It's giving me a headache.

Imagine how much worse it gets if you're mixing in some null-coalescing and null-conditional calls.

I think the -then and -else options are more in line with PowerShell syntax, especially if we overloaded -else for null-coalescing: $x = 2 * ($Size -else $DefaultSize)

I've been thinking the main gain of a conditional operator is the fact that it more explicitly outputs a value, and doesn't require wrapping in $( ) in order to feed that value to a pipeline and doesn't confuse people when you assign it's output to a variable.

I'm not sure how often I'll use it, because it's readability is painful for PowerShell. For anything non-trivial, you're going to loose most of the benefit if you have to wrap each clause in a parenthesis...

rkeithhill commented 4 years ago

I just think PS should stay PS and not try to be a C-family lookalike.

But if {} else {}, do/while/for/foreach() {}, switch () {} and try/catch/finally all came from C-family languages. I mean PS could have used if fi and case ... esac (ugh, just threw up in my mouth a little on that one). They specifically chose in the early days to follow C#'s lead WRT syntax.

The whole -<operator> approach was hotly debated in the early Monad beta days but folks (rightly) couldn't get past using > for greater than when in every other shell in the world, that's a redirection operator.

Given this conversation, I'm shocked we ever got try/catch/finally into the language. We had trap {}, why add this new-fangly developer thing to catch exceptions?

Jaykul commented 4 years ago

@rkeithhill if there had been an RFC process, you probably wouldn't have gotten try/catch unless someone implemented it first and demonstrated the performance impact πŸ˜‰

LarryWeiss commented 4 years ago

As an "inclusionist" I want both styles of syntax.
The "C" like one, and the verbose PowerShell one. The terse one I'd use at the command prompt. The verbose one I'd use in a script.

rjmholt commented 4 years ago

I think the pith motivation for a ternary is indicative; it's like an alias form of $(if / else). In which case we should favour brevity, so ? and :. That also means we can provide ?. and ?[] with null-soaking semantics (although the ternary condition should have the usual boolean-coercion falsey semantics) with some syntactical consistency.

Holistically, PowerShell has a lot of differences from C# and I don't think it should just copy along on that front, but it's clearly in the C-like syntax family I think, so we can justify the ? and : there as well.

That wouldn't stop us from adding something like -then and -else later to behave like && and || do in JavaScript/TypeScript (which also have ternary operators).

On the newline complexity thing @Jaykul mentions, I'm again motivated by the brevity/pith thing. In Python lambdas can only be on a single line (because if it's longer than that you should write a function). Perhaps in PowerShell, if your ternary needs clever newline mangling, it's time to write an if { } else { }.

rismoney commented 4 years ago

I think it's also confusing with $? usage.

$? ? $? : '?'

Why not use unused operators && and || or something else?

WithHolm commented 4 years ago

I understand while reading all of this that the tenerary, is not really a thing that is going away. people coming from a developer background wishes to have this, while powershell needs to be verbose in order to be "newbie" friendly. A "get used to this" approach is a bad idea and will just create a bigger hurdle for new users.

However.. ive been thinking: How about using it as a cmdlet, that can be pielined and hopefully with a alias (like Foreach or Where)? This makes the property easy to use, follows already existing powershell framework and does not require new defintions for the core. Most importantly it can be used in the command prompt!

$var = @{
    prop = "Yes!"
}
$var|Ternary-object{$_.prop, "no"}
#small t, as a |T looks ugly, but it could also be some symbol
$var|t prop, "no"
rismoney commented 4 years ago

Not sure if this in the realm of ternary, but is there a semantic for $foo=$bar if ($foo). Ruby has nice syntactical sugar along those lines.

glennsarti commented 4 years ago

Not sure if this in the realm of ternary, but is there a semantic for $foo=$bar if ($foo). Ruby has nice syntactical sugar along those lines.

@rismoney That is way outside the scope of the original ternary discussion. As a ruby user myself, that requires a whole other discussion.

vexx32 commented 4 years ago

@WithHolm

How about using it as a cmdlet, that can be pielined and hopefully with a alias (like Foreach or Where)? This makes the property easy to use, follows already existing powershell framework and does not require new defintions for the core. Most importantly it can be used in the command prompt!

Pipelines are not really compatible with expression syntax; an expression is only usable at the start of a pipeline. As this is intended to be used in expression syntax, that doesn't jive particularly well.

However, you could indeed make such a function relatively easily for the purposes you describe. There's nothing stopping you from doing so. Also, even with the expression syntax... you can still use it at the prompt, not sure what you mean there. All PowerShell is valid from the PowerShell prompt.

@rismoney

Why not use unused operators && and || or something else?

Because those are used for other things in Bash by convention, and intended more for use with commands rather than expressions. That is also being implemented in a currently-being-worked-on PR from @rjmholt.

@Jaykul

I'm not concerned you'll break that -- I'm asking how a user is supposed to know that's not what you're doing.

If you put the ":" on the following line, then suddenly the space or parenthesis after it is mandatory, right? Since otherwise :Whatever is a valid command name? I don't know. It's giving me a headache.

Imagine how much worse it gets if you're mixing in some null-coalescing and null-conditional calls.

I think the -then and -else options are more in line with PowerShell syntax, especially if we overloaded -else for null-coalescing: $x = 2 * ($Size -else $DefaultSize)

I don't disagree with your stance here, but I do think making the operators look too much like existing operators is asking for a difficult time for people just learning it. Ternary is not a currently available syntax in PS, and I think we'd be lax to make it look too similar to currently available syntax. It is a different syntax, and it's going to behave differently to existing binary operators; thus, it should look different to set the correct expectation.

And, as with anything new we introduce -- there's always a bit of a barrier for new folks whenever something new is added. That's why we document features; I don't think "people aren't immediately going to be familiar with it" is a great place to start at -- PowerShell as a very concept was initially so alien that it required a whole manifesto before there was sufficient buy-in for it to even get started.

PowerShell strives to be as accessible as possible, but also as flexible as possible, given that it's a shell. Elastic syntax is great for this; I'd generally consider this more as a more brief way to write if ($a) { $then } else { $otherwise } that we can already work with. Yes, it's not perfectly equivalent as we want it to live in the realm of expressions rather than keywords or commands, but it is effectively the expression equivalent -- and the if/else syntax remains available.

KirkMunro commented 4 years ago

On the newline complexity thing @Jaykul mentions, I'm again motivated by the brevity/pith thing. In Python lambdas can only be on a single line (because if it's longer than that you should write a function). Perhaps in PowerShell, if your ternary needs clever newline mangling, it's time to write an if { } else { }.

PowerShell is very verbose, so you're really reducing the usefulness of a ternary operator if you're forcing it to be used only on a single line. Even in C#, which is much more pithy than PowerShell, I span ternary operators across multiple lines all the time because (a) it reads very well, and (b) it is much easier to see what will happen when the code runs. e.g.

Runspace = _streamingHost != null
    ? RunspaceFactory.CreateRunspace(_streamingHost, iss)
    : RunspaceFactory.CreateRunspace(iss);

Carrying that same example into PowerShell as a single line would give us this:

$Runspace = $streamingHost -ne $null ? [RunspaceFactory]::CreateRunspace($streamingHost, $iss) : [RunspaceFactory]::CreateRunspace($iss)

And that's with an example that's not even indented.

Compare that with:

$Runspace = $streamingHost -ne $null
    ? [RunspaceFactory]::CreateRunspace($streamingHost, $iss)
    : [RunspaceFactory]::CreateRunspace($iss)

If implicit continuance is not supported, that would look like this:

$Runspace = $streamingHost -ne $null ?
    [RunspaceFactory]::CreateRunspace($streamingHost, $iss) :
    [RunspaceFactory]::CreateRunspace($iss)

That's a little awkward. Plus with these examples, they aren't even using actual PowerShell commands, which could result in the ternary operator symbols being far off to the right where it becomes quite difficult to see what is going on at a glance.

Going back to the -then|-else proposal, I like that syntax as an option, and could see myself using:

$Runspace = $streamingHost -ne $null `
    -then [RunspaceFactory]::CreateRunspace($streamingHost, $iss) `
    -else [RunspaceFactory]::CreateRunspace($iss)

But to get that syntax I'd need the backticks I used here or some sigil to indicate I want multi-line continuation for that command since -then and -else could be command names already in place in people's scripts.

If that was the only option for ternary, I'd just do this instead:

$Runspace = if ($streamingHost -ne $null) {
    [RunspaceFactory]::CreateRunspace($streamingHost, $iss)
} else {
    [RunspaceFactory]::CreateRunspace($iss)
}

@jaykul For your earlier example of ternary use with comments and Invoke-Command, I think that highlights a few best practices that should come out with ternary support:

  1. Ternary operators should only be used for short commands. If you have to use anything more than a single command on a single line inside a ternary operator, you should probably use an if statement instead.
  2. Comments used inside ternary operators should be brief and on a single line. If you require longer comments, you should place those comments before the ternary operator.

We could consider not allowing comments inside of a ternary (that's just a boolean flag to switch in the code) if we feel they'll cause more confusion than they're worth.

iSazonov commented 4 years ago

I think the right approach is to be very conservative in extending the language. So what is the main question that we cannot do without this operator by existing means? Today I don’t see why it’s definitely better than $var = if ( ... ) { ... } else { … }

rjmholt commented 4 years ago

I think the right approach is to be very conservative in extending the language

@iSazonov personally I'm very much with your there. I think here basically there's been an ask on this one for a while and in a restricted sense it's not a huge feature. Under ordinary circumstances I think it's worth opting on the conservative side, but in this case I think it's a nice inclusion and unusually for PowerShell, it can be implemented in a way that doesn't really step on any toes.

I span ternary operators across multiple lines all the time because (a) it reads very well, and (b) it is much easier to see what will happen when the code runs

@KirkMunro I totally agree with you there — I do the same in C#. However, my feeling there is:

I personally imagine ?/: to be an ergonomic improvement for interactive and on-the-fly PowerShell usage, and something that PSScriptAnalyzer will likely encourage you to replace with if/else in scripts deployed for long-term or shared usage. I think if you want a conditional expression and you're going to spend more characters on if/else than the body then that's what the ternary serves.

I'll admit that I don't have a strong or dogmatic feeling on this particular feature (I also like -then and -else but I think they could be added separately as non-coercing truthy/falsey expression operators). But that leads me to think it's best to keep this feature smaller.

daxian-dbw commented 4 years ago

... and something that PSScriptAnalyzer will likely encourage you to replace with if/else in scripts deployed for long-term or shared usage.

@rjmholt Personally, I don't think PSScriptAnalyzer should discourage the use of ternary operator in script. It's less nesting comparing to if {} else {}, and could be cleaner in many cases.

KirkMunro commented 4 years ago

So having ?/: with no pre-continuation...

It's pre-continuation or bust for this operator as far as I'm concerned, because without that it simply cannot add enough value to be useful, as I attempted to demonstrate in the examples above.

On the conflict/potential confusion with the ? alias for Where-Object, since aliases are not meant for use in scripts, I wonder if that helps reduce the potential for confusion with this operator, which would be for use in scripts. πŸ€”

rismoney commented 4 years ago

Aliases, especially ? can be used in scripts and have been present in the language since inception. They are first class citizens.

SeeminglyScience commented 4 years ago

@daxian-dbw

@KirkMunro Good catch. The continuation of ? will break ? -InputObject $c Count -gt 0 in that case, so it will be a potential breaking change. Personally, I'm not too worried about it. That's a very obscure use of Where-Object and I presume the possibility of actually breaking any scripts is very low. But we do need the committee review to approve this.

Would it be feasible to fall back to command parsing if : is not included?

e.g.

# Parse as ternary:
0 -eq 1
    ? "something"
    :  "nothing"

# Parse as binary expression; command; string constant:
0 -eq 1
    ? "something"
    "nothing"

I haven't looked at what parts of resync are expensive, but if it's the actual act of falling back and not the saving of state, the impact should be minimal yeah?

rjmholt commented 4 years ago

@SeeminglyScience that example in particular is worrying to me; it's a naturally LR grammar construct that we must parse with an LL parser. We have to do arbitrary lookahead to cross the tokens of the first expression to know what kind of token and therefore what syntactic element we are looking at. That conflicts with the essential structure of the PowerShell parser.

Here is a fuller explanation of what I'm talking about.

MSAdministrator commented 4 years ago

I think that that PowerShell should use the same/similar syntax as Python.

$False if $x -eq 1 else $True

Jaykul commented 4 years ago

The more we debate this, the more the -then and -else options look better to me.

However, the more I think about that (and go and re-read @BrucePay's arguments for not adding this) the more I wonder why we are doing this at all. PowerShell doesn't really need to be terse.

But if people actually think the brackets make things unreadable, why not just ...

Make the parenthesis optional and allow the use of then instead

Of course, we risk looking like VB instead C#, but if we can make the parser handle ?: then certainly we can make it handle if then else without braces?

It would basically be the same as adding the ternary operator with -then and -else except you'd have to write the if keyword on the front. Putting that on the front should make most of the opposition about readability go away, and if we choose not to need the - on the front of then and else then it works out to the same number of keystrokes πŸ™„

$Runspace = if $streamingHost -ne $null 
    then [RunspaceFactory]::CreateRunspace($streamingHost, $iss)
    else [RunspaceFactory]::CreateRunspace($iss)

Is definitely somewhat more readable than:

$Runspace = $streamingHost -ne $null
    -then [RunspaceFactory]::CreateRunspace($streamingHost, $iss)
    -else [RunspaceFactory]::CreateRunspace($iss)

And much more readable than:

$Runspace = $streamingHost -ne $null
    ? [RunspaceFactory]::CreateRunspace($streamingHost, $iss)
    : [RunspaceFactory]::CreateRunspace($iss)

Of course, this does not actually solve any problems, and any change like this opens a can of worms with regards to additional syntax changes. In fact, this change as proposed, is clearly more complicated (in terms with colliding with existing syntax) and less useful (in terms of new functionality) than most syntax changes that could be proposed.

rkeithhill commented 4 years ago

While I could maybe see adding -then|-else operators, please do not add then as a new keyword. PowerShell is not an if/then language ala Basic. And at some point, unless you can achieve something close to the pithiness of ?: then don't bother. We already have an existing solution which I do use, I just really don't like its verbosity/ceremony for such a simple concept.

vexx32 commented 4 years ago

Exactly. If you want the verbosity, we have if statements already, and there's nothing wrong with them.

The ternary is just an expressive shorthand. πŸ€·β€β™‚

TobiasPSP commented 4 years ago

The more we debate this, the more the -then and -else options look better to me.

Some thoughts from someone busy teaching beginners and advanced users alike how to use PS to its fullest (and I may be terribly wrong with some observations yet am thrilled to see this lively public discussion prior to making changes to the language):

KirkMunro commented 4 years ago

On the potential confusion because of the ? alias for Where-Object, I was thinking this weekend that we just need to change the way we think about ?. Instead of just looking at ? as an alias, we need to teach/look at it as a context-sensitive conditional check. What it checks and what happens as a result depends on the context in which it is used. This shouldn't be much more difficult to learn then an operator that can be used as either an unary operator or a binary operator.

TobiasPSP commented 4 years ago

I thought exactly the same this morning πŸ˜€ ? complements the existing ?-alias very well.

Von meinem iPhone gesendet

Am 21.07.2019 um 14:19 schrieb Kirk Munro notifications@github.com:

On the potential confusion because of the ? alias for Where-Object, I was thinking this weekend that we just need to change the way we think about ?. Instead of just looking at ? as an alias, we need to teach/look at it as a context-sensitive conditional check. What it checks and what happens as a result depends on the context in which it is used. This shouldn't be much more difficult to learn then an operator that can be used as either an unary operator or a binary operator.

β€” You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.