-
Notifications
You must be signed in to change notification settings - Fork 7.1k
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
implement indexer in loop #14724
Comments
What specific problems does this solve? |
What specific problems does this solve in many languages like python, ruby, perl, rust, javascript, Go, Scala, Php....etc |
I like the idea; it complements @iRon7's proposal to introduce an automatic @doctordns, it's helpful syntactic sugar; just a couple of use cases off the top of my head; I'm sure there are more. Note: The original syntax was changed to reflect the later discussion; the index variable now comes after the iteration expression, separate with # Parellel array processing
$a = 'foo', 'bar'
$b = 'baz', 'qux'
$c = 'and' 'so on'
foreach ($aElem in $a; $i) { # WISHFUL THINKING
$bElem, $cElem = $b[$i], $c[$i]
# ...
}
# Get only every 2nd element
$a = 'foo', 'bar', 'baz', 'quux'
foreach ($aElem in $a; $i) { # WISHFUL THINKING
if ($i % 2) { continue }
} As a largely moot aside: Quite some time ago, in #3830, I had proposed allowing syntax such as |
I'd move that indexer variable to after the condition and if it's optional you don't break existing code e.g.: foreach ($item in $items, $ndx) { ... } FWIW it do not like the idea of an automatic variable like |
@rkeithhill, However,
In nested loops, |
Good point on the
|
it's not break change and it not break the loop:
|
IMO PowerShell already has too many magic variables. And to be clear, I do like the overall concept. |
yes ps has many magic variable but it not has an indexer magic variable |
another idea is to implement an indexer in $foreach enumerator
|
I think you wouldn't get too far before folks demanded a way to access an outer loop's indexer. At that point, I don't think you need (or want) two ways to access indexers. |
Agreed, @rkeithhill, but my point was that you would have a choice, i.e. I was advocating for both proposals to be implemented: if you do need cross-hierarchy index access, use the explicit syntax; otherwise, rely on In short: I think we can conclude this part of the discussion, unless enough people feel strongly enough to revisit this, in a separate proposal. Come to think of it: Also, it would leave the door open for a further, separate enhancement that could further simplify the parallel-collection enumeration use case by using a destructuring assignment as follows (which could still be combined with an explicit index variable): $a = 'foo', 'bar'
$b = 'baz', 'qux'
$c = 'and' 'so on'
foreach ($aElem, $bElem, $cElem in $a, $b, $c) { # WISHFUL THINKING
# $aElem is $a[<ndx>]
# $bElem is $b[<ndx>]
# $cElem is $c[<ndx>]
} The largest among the RHS collections would drive the number of iterations, and the iterator variables would contain Update: See #14732 |
I think the common point is that powershell lacks an indexer variable like many other languages ... now how to implement this indexer ... there are several ways on the design side: powershell way
or
the traditional way
|
@p0W3RH311 I'm with you on the "common point". I like the feature suggestion. I'm just not a fan of using a magic variable for the indexer. 🤷♂️ |
It probably won't work with
|
This isn't a very compelling argument imo. There are a lot of patterns in other languages that just don't make as much sense in PowerShell. It's definitely useful occasionally to have an index in PowerShell, but with the way pipelines work it's significantly less important. Personally I worry that this would be used too infrequently to warrant language changes. |
Agreed re the general point that not all patterns are a good fit for PowerShell, however:
Can you elaborate on how the pipeline mechanism makes this feature less important? #13772 makes a pretty good case for a an automatic To me, it's a useful enhancement, both in
|
In other languages it's very common to allocate an array up front and then populate it. Or mutate an array in place. In PowerShell it's significantly easier to just let the pipeline make one for you.
With the exception of "Combine single arrays into columns" I don't really see any real world examples in that issue. I think it's just as unlikely to see all that much usage.
Symmetry is nice, but I disagree that it's a compelling enough reason on it's own. |
This comes down to whether one indeed sees the utility, which is ultimately a subjective assessment. Let's see how the community feels. |
I've created a new issue for the separate Also, to make it more obvious how people feel about this issue, I encourage everyone who was commented so far to give the initial post here a thumbs-up or -down. |
Sorry, to clarify, I was giving examples of some of the most common scenarios one would use an index in other languages. Scenarios that are significantly less common in PowerShell. |
I see, but the fact there are additional scenarios in other languages where indices are not only useful but required as an intrinsic part of the enumeration is ultimately a moot point. To put it differently: this argument doesn't preclude utility in PowerShell scenarios. The relevant question is: Is the feature useful in PowerShell? It is to me, and I have personally wished for it in the past; we'll see how the community feels over time. |
That's correct. It was in response to your question asking why the pipeline makes it less important in PowerShell.
I think one of the best ways to show the utility would be to point to some examples of existing code that would greatly benefit from this. Like some code in an already published module or script that would be simplified significantly. I don't doubt that it has uses. I've wanted it myself a few times, but in all of those few times either a |
in PS there are many variable too infrequently used like |
Fair point, but I was confused by your mentioning the pipeline specifically, because enumeration of collections with Optionally - if the business logic rather than the technical underpinnings requires it - having an automatically maintained index at one's disposal, without having to forgo the convenience of
Point taken, but even though we are undoubtedly talking about syntactic sugar here, such sugar is often important for sweetening the developer experience, if you will. I'll see if I can come up with examples in published code and/or additional compelling examples. |
Ah yes sorry it wasn't obvious what I was referring to, but most things use the pipeline plumbing at some point. For example: $a = foreach ($b in (0..30)) { $b }
# or
$a = gci
# or
$a = $(0; 1) Behind the scenes that'll use a very similar code path to build |
Good point, @SeeminglyScience, and thanks for clarifying. Even though it's technically not entirely accurate, I generally suggest using and interpreting the term "pipeline" as informal shorthand for "connecting commands with a pipe symbol" (including the case where the first segment is an expression; e.g. Here, we have the contrast between the Therefore, in the pipeline (loosely speaking), the proposed automatic index variable must be implicit too, with a name such as (As stated, it would make sense to me to also allow implicit iterator variables in The following uses cases are more directly relevant to #13772, but since the unified discussion seems to be happening here, here goes: In the pipeline, it isn't just
In both cases the script blocks run in a child scope - see #7157 - which makes maintaining a cross-invocation index variable via the parent scope both obscure and cumbersome: Consider the scenario of renaming files to names that incorporate a sequence number via a delay-bind script block (a real-world scenario that comes up repeatedly on Stack Overflow): # Delay-bind script block
# Cumbersome and obscure, because you must refer to the $i variable in the *parent* scope.
# Renames the input files to "file1.txt", "file2.txt", ...
$i = 0
Get-Item *.txt | Rename-Item -NewName { "file" + ++(Get-Variable -Scope 1 i).Value + ".txt" } -WhatIf With # WISHFUL THINKING.
Get-Item *.txt | Rename-Item -NewName { "file" + (1 + $PSIndex) + ".txt" } -WhatIf The same applies to a (script block-based) calculated property; consider the case of wanting to create sequence number-based identifiers via # WISHFUL THINKING.
Get-Item *.txt | Select-Object @{ n='Id'; e={ 1 + $PSIndex } }, FullName While the scoping problem doesn't arise with the # WISHFUL THINKING
foreach ($file in Get-Item *.txt; $i) {
[pscustomobject] @{
Id = 1 + $i
FullName = $file.FullName
}
} |
Yeah it doesn't use the pipeline for enumeration, though it can emit to an output pipe still.
If you're renaming files, aren't you going to want to keep the number of the existing name? Like if you're renaming The rest of the examples are similar in that while it is an example of how it could be used, it's not clear why you'd need to. |
If I understand correctly, directly only stand-alone (possibly in the context of an assignment); in a pipeline (with
|
Anything that isn't captured or in a class method is emitted to the pipeline. For example: This is what the script `0` compiles toprivate static void <ScriptBlock>(FunctionContext funcContext)
{
try
{
context = funcContext._executionContext;
locals = ((MutableTuple<object, Object[], object, object, PSScriptCmdlet, PSBoundParametersDictionary, InvocationInfo, string, string, Null, Null, Null, Null, Null, Null, Null>)funcContext._localsTuple);
funcContext._functionName = @"<ScriptBlock>";
funcContext._currentSequencePointIndex = 0;
context._debugger.EnterScriptFunction(funcContext);
try
{
funcContext._currentSequencePointIndex = 1;
if (context._debuggingMode > 0)
{
context._debugger.OnSequencePointHit(funcContext)
}
funcContext._outputPipe.Add(((object)0));
context.QuestionMarkVariableValue = true;
}
catch (Exception exception)
{
ExceptionHandlingOps.CheckActionPreference(funcContext, exception);
}
funcContext._currentSequencePointIndex = 2;
if (context._debuggingMode > 0)
{
context._debugger.OnSequencePointHit(funcContext);
}
}
finally
{
context._debugger.ExitScriptFunction();
}
} The relevant line being: funcContext._outputPipe.Add(((object)0));
My suggestion if your aim is to convince someone to pick up this work (or convince someone that this should be brought up with the committee) would be to use real world examples. Theoretical examples aren't as helpful for determining the level of impact a feature could provide. |
Yes, but my point was that you can't do
Honestly, to me the examples given so far - both concrete and abstract - are so self-evidently compelling that I don't see value in spending more time on finding real-world examples. Perhaps others feel inspired to do so, and perhaps the count of thumbs-up on the issue will make a statement of its own over time. The primary hurdle, I'd say, is to get this - and #13772 - committee-reviewed; I wouldn't want anyone spending time on an implementation without knowing that the feature will be approved. |
Just yesterday, I thought PowerShell is at the same conceptual level as it was 20 years ago and is still at the foot of a magnificent tower - we haven't even climbed one floor in all this time. One must have limitless inspiration to develop primary ideas to these new heights. |
Thanks, @iSazonov. I too value the conceptual elegance of PowerShell's OO pipeline and welcome improvements to it, but, as is often the case, this is not an either-or proposition (more on that later). Unquestionable, what was initially proposed here in the context of the So, if you already think #13772 is worthwhile, please tag it for committee review. (The generalization of #13772, which started with a focus on
Cmdlets such as Neither is going away. Both should provide an expressive developer experience with (fundamental) feature parity (which, regrettably, already falls short in one case: the A It comes down to a tradeoff between memory use and performance, and while in many cases you may be able to choose any of these without too much real-world impact, there are cases where are forced to choose one ( All three serve the same fundamental purpose: iterating over something enumerable (loosely speaking; strictly speaking, it is the pipeline itself that does the enumeration in the case of All three are expressive in that you needn't worry about the details of the enumeration: is it an index-based collection or a (potentially lazy) enumerable?
Providing an automatic index indicating the By contrast, If such an automatically maintained index makes sense with I've said it before: the examples in #13772 and here to me amply demonstrate that such an automatic index is called for as expressive syntactic sugar that serves real-world needs, which, as @p0W3RH311, has pointed out, several other languages have recognized too (notably including the systems programming language Rust) Syntactic sugar isn't a luxury; it's what makes a language enjoyable to use and boosts productivity. And here's a secret:
And with Also note how the
|
I have to say that whenever I've needed something like this, I've just changed to a I don't mind the idea of adding syntax to foreach($item in @($List); $index) {
$item | add-member Index $index -PassThru
} Really better than this: $index=0; foreach($item in @($List)) {
$item | add-member Index ($index++) -PassThru
} Remember that it's not just about saving you a few keystrokes, it's also about clarity and readability ... To make it clearer, we could add a parameter (there's precedent in foreach($item in @($List)) -counter index {
$item | add-member Index $index -PassThru
} |
To me it is unequivocally better:
Agreed, but to me Yes, |
After discussing this with the Engine working group, we don't think this should be baked in at the language/syntax level. This is probably best accomplished with a function like Python's enumerate. This could be implemented in an external module and later evaluated for inclusion in PowerShell. |
Hi guys !
loops in powershell is various and its awesome but it missing an indexer for example:
is more elegant and short:
The text was updated successfully, but these errors were encountered: