|
| 1 | +--- |
| 2 | +RFC: RFC0034 |
| 3 | +Author: Michael Klement |
| 4 | +Status: Rejected |
| 5 | +Version: 0.1 |
| 6 | +Area: Operators |
| 7 | +Comments Due: 7/8/2018 |
| 8 | +Plan to implement: No |
| 9 | +--- |
| 10 | + |
| 11 | +# Expose the .ForEach() and .Where() collection-transforming/filtering methods as regular PowerShell operators |
| 12 | + |
| 13 | +[The `.ForEach()` and `.Where()` methods](http://www.powershellmagazine.com/2014/10/22/foreach-and-where-magic-methods/) introduced in PSv4 are better-performing and feature-richer expression-mode alternatives to the `ForEach-Object` and `Where-Object` cmdlets; they complement the memory-throttling but slow pipeline processing by the cmdlets with fast, all-in-memory, collected-up-front collection processing. |
| 14 | + |
| 15 | +Semantically, these methods act _like_ operators (and are even referred to a such in source code and documentation fragment), but are syntactically implemented as _methods_. |
| 16 | + |
| 17 | +Since PowerShell's _own_ functionality (as opposed to .NET functionality made _accessible_ by it) is surfaced as commands (cmdlets/function/scripts) and _bona fide operators_ such as `-match`, `.ForEach()` and `.Where()` should (also) be surfaced as array-valued binary operators `-foreach` and `-where`. |
| 18 | + |
| 19 | +## Motivation |
| 20 | + |
| 21 | +As a regular PowerShell user |
| 22 | +I can use operators `-foreach` and `-where` |
| 23 | +in order to transform/filter in-memory collections efficiently, using familiar operator syntax and `$_`-based script blocks, as conceptually clean complements to the `ForEach-Object` and `Where-Object` cmdlets. |
| 24 | + |
| 25 | +Examples: |
| 26 | + |
| 27 | +```powershell |
| 28 | +$var = 1, 2, 3 -foreach { $_ + 1 } # wishful thinking |
| 29 | +
|
| 30 | +# vs. |
| 31 | +$var = foreach ($i in 1, 2, 3) { $i + 1 } |
| 32 | +# or (slower) |
| 33 | +$var = 1, 2, 3 | ForEach-Object { $_ + 1 } |
| 34 | +# --- |
| 35 | +
|
| 36 | +$var = 1, 2, 3 -where { $_ -gt 1 } # wishful thinking |
| 37 | +
|
| 38 | +# vs. |
| 39 | +# (slower) |
| 40 | +$var = 1, 2, 3 | Where-Object { $_ -gt 1 } |
| 41 | +# or (conceptually *indirect*, given that there's no filtering loop): |
| 42 | +$var = foreach ($i in 1, 2, 3) { if ($i -gt 1) { $i } } |
| 43 | +``` |
| 44 | + |
| 45 | +Advanced example (no `Where-Object` counterpart): split a collection in two: |
| 46 | + |
| 47 | +```powershell |
| 48 | +$odds, $evens = 1, 2, 3, 4 -where { $_ % 2 }, 'split' |
| 49 | +
|
| 50 | +# equivalent to: |
| 51 | +$odds, $evens = (1, 2, 3, 4).Where({ $_ % 2 }, 'split') |
| 52 | +``` |
| 53 | + |
| 54 | + |
| 55 | +## Specification |
| 56 | + |
| 57 | +* `.ForEach()` and `.Where()` will be exposed as _binary operators_ `-foreach` and `-where`, respectively. |
| 58 | + |
| 59 | +* Unlike the methods (which return `[System.Collections.ObjectModel.Collection[psobject]]` instances), the operators will return `[object[]]` arrays for consistency with existing array-aware operators such as `-eq` and `-match`. |
| 60 | + |
| 61 | +* The _optional_ arguments supported by the `.ForEach()` and `.Where()` methods will be surfaced as optional RHS array elements, analogous to the `-split` operator's optional arguments, for instance - see below. |
| 62 | + |
| 63 | +* The new operators will have the same precedence as the group of equal-precedence operators that operator `-like` falls into - see [`Get-Help about_Operator_Precedence`](https://github.com/PowerShell/PowerShell-Docs/blob/staging/reference/6/Microsoft.PowerShell.Core/About/about_Operator_Precedence.md) |
| 64 | + |
| 65 | +* In terms of handling errors occurring in the RHS script block as well as `return`, `continue` and `break` semantics there, the new operators will exhibit the same behavior as the `.ForEach()` and `.Where()` methods. |
| 66 | + |
| 67 | +### Syntax forms (meta syntax borrowed from [`about_Split`](https://github.com/PowerShell/PowerShell-Docs/blob/staging/reference/6/Microsoft.PowerShell.Core/About/about_Split.md])) |
| 68 | + |
| 69 | +#### `-foreach` |
| 70 | + |
| 71 | +For script-block-based transformation, optionally with arguments passed to each script block invocation. |
| 72 | + |
| 73 | +```none |
| 74 | +<collection> -foreach <ScriptBlock>[, <Arguments[]>] |
| 75 | +``` |
| 76 | + |
| 77 | +For type conversion. |
| 78 | +Note: |
| 79 | + |
| 80 | + * Arguably, this form is not needed, because a simple array-valued cast will do: |
| 81 | +`[string[]] (1, 2, 3)` rather than `1, 2, 3 -foreach [string]` |
| 82 | + |
| 83 | + * However, for symmetry with .ForEach() it should probably be implemented. |
| 84 | + |
| 85 | +```none |
| 86 | +<collection> -foreach <Type> |
| 87 | +``` |
| 88 | + |
| 89 | +For property access / method calls: |
| 90 | +Note: |
| 91 | + |
| 92 | +* For mere property extraction, there's a simpler alternative: member enumeration: |
| 93 | +`((get-date), (get-date)).Ticks` vs. `(get-date), (get-date) -foreach 'Ticks'` |
| 94 | + |
| 95 | +* However, there's actually a distinct advantage to using `-foreach`: bypassing the _ambiguity_ of member enumeration: |
| 96 | +`('ab', 'cde') -foreach 'Length'` will unambigiously access the _elements'_ `.Length` property, |
| 97 | +whereas `('ab', 'cde').Length` returns the _array's_ length (element count). |
| 98 | + |
| 99 | +```none |
| 100 | +<collection> -foreach "propertyName"[, <value>] |
| 101 | +<collection> -foreach "methodName"[, <Arguments[]>] |
| 102 | +``` |
| 103 | + |
| 104 | +#### `-where` |
| 105 | + |
| 106 | +Filtering a collection based on the Boolean outcome of a script block, optionally in one of serveral modes and with a limit on how many objects to return: |
| 107 | + |
| 108 | +```none |
| 109 | +<collection> -where <ScriptBlock>[, <mode>[, <numberToReturn>]] |
| 110 | +``` |
| 111 | + |
| 112 | +* `<mode>` is technically a [`[System.Management.AutomationWhereOperatorSelectionMode]`](https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.whereoperatorselectionmode?view=powershellsdk-1.1.0) enumeration value, but may be specified as a string (e.g., `'Split'`) |
| 113 | +* The semantics of `[int]` `<numberToReturn>` depend on `<mode>`; `0`, the default, requests that _all_ matching elements be returned. |
| 114 | + * Given https://github.com/PowerShell/PowerShell/issues/4765, consider interpreting negative values - which currently generate an execption - as requests to return elements from the _end_ of the results collection - this should obviously apply both to the method and operator forms. |
| 115 | +* As with `-split`, arguments are strictly positional; notably, specifying `<numberToReturn>` requires that `<mode>` value also be specified, even if as `'Default`. |
| 116 | + |
| 117 | +[@KirkMunro's blog post](http://www.powershellmagazine.com/2014/10/22/foreach-and-where-magic-methods/) contains the details. |
| 118 | + |
| 119 | +It's probably worth creating a new conceptual help topic titled `about_Collection_Operators` to describe these new operators, with links to existing help topics for those existing operators that _also_ support collection-valued LHS input, such as `-eq`, `-like`, and `-replace`, among others. |
| 120 | + |
| 121 | +## Alternate Proposals and Considerations |
| 122 | + |
| 123 | +The alternative is to make do with the existing _method_ implementation, restricting their use to a more developer-savvy crowd comfortable with method syntax. |
| 124 | + |
| 125 | +Note that even the existing methods [lack proper documentation](https://github.com/PowerShell/PowerShell-Docs/issues/2307) as of this writing. |
0 commit comments