|
| 1 | +--- |
| 2 | +RFC: 0002 |
| 3 | +Author: Jason Shirk |
| 4 | +Status: Draft |
| 5 | +Area: Splatting |
| 6 | +Comments Due: 3/31/2016 |
| 7 | +--- |
| 8 | + |
| 9 | +# Generalized Splatting |
| 10 | + |
| 11 | +Splatting was introduced in PowerShell v2 |
| 12 | +as a way of passing named arguments to a command |
| 13 | +using an expression (e.g. `@PSBoundParameters`) |
| 14 | +instead of explicit parameter syntax (e.g. `-Param1 $value`). |
| 15 | +The expression syntax introduced was limited |
| 16 | +to variable references and is more restrictive than necessary. |
| 17 | + |
| 18 | +Proxy functions are much easier to write using splatting. |
| 19 | +A proxy function might add or remove parameters to another command, |
| 20 | +but might not care about all of the parameters to the proxied command. |
| 21 | +The proxy still needs to pass those parameters through to the proxy, |
| 22 | +and it does this with splatting. |
| 23 | + |
| 24 | +Scripters have found splatting useful beyond proxy functions, |
| 25 | +but the current syntax limits broader use of splatting. |
| 26 | + |
| 27 | +## Motivation |
| 28 | + |
| 29 | +One common use of splatting is to provide an alternate syntax for calling a command with many parameters. |
| 30 | +For example, consider a command like the following |
| 31 | + |
| 32 | +```PowerShell |
| 33 | +Update-TypeData -TypeName MyCustomType ` |
| 34 | + -MemberName MyCustomProperty ` |
| 35 | + -MemberType NoteProperty ` |
| 36 | + -Value 42 |
| 37 | +``` |
| 38 | + |
| 39 | +In the above example, note the use of backticks at the end of every line. |
| 40 | +Those backticks are difficult to read and easy to forget. |
| 41 | +Splatting can help some here: |
| 42 | + |
| 43 | +```PowerShell |
| 44 | +$addTypeParams = @{ |
| 45 | + TypeName = 'MyCustomType' |
| 46 | + MemberName = 'MyCustomProperty' |
| 47 | + MemberType = 'NoteProperty' |
| 48 | + Value = 42 |
| 49 | +} |
| 50 | +Update-TypeData @addTypeParams |
| 51 | +``` |
| 52 | + |
| 53 | +This works, but feels a bit messy because of the need for a variable, |
| 54 | +when the hashtable could start on the same line as the command name. |
| 55 | + |
| 56 | +Another common use of splatting is to remove a specific parameter when splatting: |
| 57 | + |
| 58 | +```PowerShell |
| 59 | +$PSBoundParameters.Remove('SomeExtraParam') |
| 60 | +Command @PSBoundParameters |
| 61 | +``` |
| 62 | + |
| 63 | +This proposal suggesst a syntax that improves this scenario as well. |
| 64 | + |
| 65 | +## Specification |
| 66 | + |
| 67 | +This RFC proposes a more generalized syntax for splatting to support: |
| 68 | + |
| 69 | +- Splatting general expressions (as opposed to simple variables) |
| 70 | +- Splatting in method invocations |
| 71 | +- Splatting in switch cases |
| 72 | + |
| 73 | +Today, the syntax for splatting is a sigil used on a variable name. |
| 74 | +This proposal expands the use of the sigil to be allowed on an expression. |
| 75 | +For example: |
| 76 | + |
| 77 | +```PowerShell |
| 78 | +# Existing usage: |
| 79 | +command @PSBoundParameters |
| 80 | +
|
| 81 | +# Equivalent - but splatting an expression (the value of a variable) |
| 82 | +command @$PSBoundParameters |
| 83 | +
|
| 84 | +# Splat another expression - a hashtable |
| 85 | +Update-TypeData @@{ |
| 86 | + TypeName = 'MyCustomType' |
| 87 | + MemberName = 'MyCustomProperty' |
| 88 | + MemberType = 'NoteProperty' |
| 89 | + Value = 42 |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +Note the two '@' characters in the hashtable example above. |
| 94 | +If the second '@' was omitted, the example would pass a hashtable (no splatting) |
| 95 | +in all versions of PowerShell, even V1, which means splatting without the second '@' |
| 96 | +would be a breaking change. |
| 97 | + |
| 98 | +We can use more general expressions as well: |
| 99 | + |
| 100 | +```PowerShell |
| 101 | +# Call a command that returns a hashtable or array to splat |
| 102 | +Get-ChildItem @$(Get-ChildItemArgs) |
| 103 | +
|
| 104 | +# a method that returns a hashtable or array to splat |
| 105 | +Invoke-Something @$($obj.GetInvokerArgs()) |
| 106 | +``` |
| 107 | + |
| 108 | +### Relaxed splatting |
| 109 | + |
| 110 | +In some cases, it is desirable to splat only the arguments that are available, |
| 111 | +and ignore the others. |
| 112 | +We will call this relaxed splatting. |
| 113 | +For example: |
| 114 | + |
| 115 | +```PowerShell |
| 116 | +$myArgs = @{ Path = $pwd; ExtraStuff = 1234 } |
| 117 | +Get-ChildItem @$myArgs |
| 118 | +``` |
| 119 | + |
| 120 | +The above example would fail with a "parameter not found" because of the 'ExtraStuff' key. |
| 121 | +Here is a possible syntax to allow the above without resulting in an error: |
| 122 | + |
| 123 | +```PowerShell |
| 124 | +$myArgs = @{ Path = $pwd; ExtraStuff = 1234 } |
| 125 | +Get-ChildItem @?$myArgs |
| 126 | +``` |
| 127 | + |
| 128 | +We can think of '@?' as the 'relaxed splatting' operator. |
| 129 | +If '@' is the splatting operator, |
| 130 | +adding the '?' is suggestive of being more permissive, |
| 131 | +much like the C# '?.' member access operator. |
| 132 | + |
| 133 | +### Splatting in method invocations |
| 134 | + |
| 135 | +Today, named arguments are only supported when calling commands, |
| 136 | +named arguments do not work when calling methods. |
| 137 | +PowerShell could adopt a C# like syntax to name arguments, |
| 138 | +but splatting provides a similar capability with some additional flexibility. |
| 139 | +The obvious syntax would mirror command invocation: |
| 140 | + |
| 141 | +```PowerShell |
| 142 | +# Splat a hashtable defined outside the method call |
| 143 | +$subStringArgs = @{startIndex = 2} |
| 144 | +$str.SubString(@$subStringArgs) |
| 145 | +
|
| 146 | +# Splat a hashtable defined inline in the method call |
| 147 | +$str.SubString(@@{startIndex = 2; length=2}) |
| 148 | +``` |
| 149 | + |
| 150 | +Mixing splatting and positional arguments is supported. |
| 151 | + |
| 152 | +```PowerShell |
| 153 | +$str.SubString(2, @@{length=2}) |
| 154 | +``` |
| 155 | + |
| 156 | +A runtime check (or parse time if possible) is necessary to make sure an argument is not specified positionally |
| 157 | +and via splatting in the same invocation: |
| 158 | + |
| 159 | +```PowerShell |
| 160 | +# Must be an error, parse time or runtime, because startIndex |
| 161 | +# is specified positionally and via splatting. |
| 162 | +$subStringArgs = @{startIndex = 2} |
| 163 | +$str.SubString(2, @$subStringArgs) |
| 164 | +``` |
| 165 | + |
| 166 | +Multiple splatted arguments are not allowed: |
| 167 | + |
| 168 | +```PowerShell |
| 169 | +# Error, multiple splatted arguments |
| 170 | +$str.SubString(@@{startIndex = 2}, @@{length=2}) |
| 171 | +``` |
| 172 | + |
| 173 | +The splatted argument must be last. |
| 174 | + |
| 175 | +```PowerShell |
| 176 | +# Error, splatted argument is not last |
| 177 | +$str.SubString(@@{length=2}, 2) |
| 178 | +``` |
| 179 | + |
| 180 | +### Splatting in switch cases |
| 181 | + |
| 182 | +It can be awkward to match multiple conditions with a single switch statement. |
| 183 | +For example: |
| 184 | + |
| 185 | +```PowerShell |
| 186 | +switch ($color) { |
| 187 | + { $_ -eq 'Red' -or $_ -eq 'Blue' -or $_ -eq 'Green' } { $true } |
| 188 | + default { $false } |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +With splatting, this can be simplified: |
| 193 | + |
| 194 | +```PowerShell |
| 195 | +switch ($color) { |
| 196 | + @@('Red','Blue','Green') { $true } |
| 197 | + default { $false } |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +### Modifying hashtables for splatting |
| 202 | + |
| 203 | +Sometimes it is useful to provide a 'slice' of a hashtable, |
| 204 | +e.g. you want to remove or include specific keys. |
| 205 | +The Add/Remove methods on a hashtable work, but can be verbose. |
| 206 | +This proposal suggests overloading the '+' and '-' operators to provide a hashtable 'slice': |
| 207 | + |
| 208 | +```PowerShell |
| 209 | +Get-ChildItem @$($PSBoundParameters - 'Force') # Splat all parameters but 'Force' |
| 210 | +Get-ChildItem @$($PSBoundParameters - 'Force','WhatIf') # Splat all parameters but 'Force' and 'WhatIf' |
| 211 | +Get-ChildItem @$($PSBOundParameters + 'LiteralPath','Path') # Only splat 'LiteralPath' and 'Path' |
| 212 | +``` |
| 213 | + |
| 214 | +Today, PowerShell supports "adding" two hashtables with the '+' operator, |
| 215 | +the result is a new hashtable with all of the key value pairs from both hashtables. |
| 216 | +It is an error for a key to be specified in both operands. |
| 217 | + |
| 218 | +This proposal adds semantics for adding and subtracting other values. |
| 219 | +If the value is enumerable, the effect is to treat each value in the collection as a key, |
| 220 | +otherwise the value is treated as a single key. |
| 221 | +The result is always a new hashtable, |
| 222 | +the left hashtable operand is unmodified. |
| 223 | + |
| 224 | +When using '+', the result will only include keys found in the right hand side. |
| 225 | +When using '-', the result will exclude all keys from the right hand side. |
| 226 | + |
| 227 | +In either case, |
| 228 | +it is not an error to specify a key in the right hand side operand that is not present in the left hand side. |
| 229 | + |
| 230 | +## Alternate Proposals and Considerations |
| 231 | + |
| 232 | +### Slicing operators |
| 233 | + |
| 234 | +The suggested use of '+' and '-' is perhaps surprising |
| 235 | +even though they correspond to Add and Remove, respectively. |
| 236 | +The actual operation is also similar to a union or intersection, |
| 237 | +so other operators should be considered, perhaps bitwise operators |
| 238 | +like '-bnot' and '-bor', or maybe new general purpose set operators. |
| 239 | + |
| 240 | +### Postfix operator |
| 241 | + |
| 242 | +The use of a sigil is not always well received. |
| 243 | +This proposal nearly considers '@' a prefix unary operator, |
| 244 | +but it doesn't quite specify it as such. |
| 245 | + |
| 246 | +A postfix operator is another possiblity and would look less like a sigil. |
| 247 | +This idea was rejected because, when reading a command invocation from left to right, |
| 248 | +it's important to understand how a hash literal is to be used. |
| 249 | +The sigil makes it clear a hash literal is really specifying command arguments. |
| 250 | +Furthermore, the sigil simplifies the analysis required for good parameter completion, |
| 251 | +and does not require a complete expression to begin providing parameter name completion. |
0 commit comments