I would like to refer to previously defined fields within HashTable:
@{
foo = 'bar'
baz = 'foo' + $_.foo
}
This is very handy for number of situations, particularly handling of configuration stuff.
Current workaround is to do something like:
$cfg = @{
foo = 'bar'
}
$cfg.baz = 'foo' + $cfg.foo
which doesn't look nearly that nice.
Backward compatibility might be problematic here, for example:
[pscustomobject]@{foo='A'} | % { @{
foo = 'bar'
baz = 'foo' + $_.foo
}}
If there is interest, I could try to find out backward compatible solution, otherwise feel free to close this.
You could:
```powershell
$x = @{Foo='Bar}
$x.Baz = 'Foo' + $X.foo
````
I would like to refer to previously defined fields within HashTable:
@{ foo = 'bar' baz = 'foo' + $_.foo }
That would change the behavior of existing code, e.g. this would break:
Get-ChildItem | ForEach-Object {
[PSCustomObject]@{
Something = $_.Name
}
}
You could:
$x = @{Foo='Bar} $x.Baz = 'Foo' + $X.foo
I already provided such thing in original post. I know I can do it that way, but its ugly. This is syntax sugar category.
That would change the behavior of existing code, e.g. this would break:
I also provided that. $_ is not good choice for sure.
Regarding symbol maybe $$
@{ x = 1; y = $$.x }
. Constructs such as $$.x
currently are not possible in any context AFAIK.
I get the desire for the feature, but I don't think adding another automatic variable is the way to go (besides, $$
already means something else; generally, introducing an automatic variable solely for a very specific scenario sounds problematic).
There is a _half_-ugly solution; perhaps it will do:
PS> [ordered] @{
foo = ($foo = 'bar')
baz = 'foo' + $foo
}
Name Value
---- -----
foo bar
baz foobar
We could _potentially_ use $this
for this kind of thing, but that too has a (lesser) potential to break something.
There is a half-ugly solution; perhaps it will do:
Mhm.... half-ugly ? :)
BTW, this is possible in YAML and some extensions of JSON.
but I don't think adding another automatic variable is the way to go
I doubt there is another way to go. This is basically $this
but out of classes. One other solution that comes to mind is to allow usage of its own variable
$this = @{
x = 1
y = $this.y
}
This is surprising but that is basically how YAML does it (&id and later *id). Maybe with some different syntax such as
[HashTable($id)]@{
x = 1
y = $id.y
}
We could _potentially_ use
$this
for this kind of thing, but that too has a (lesser) potential to break something.
$this
is just another variable. Any variable can break existing scritps including funky ones such as ${~}
.. .although it does seem that at least for ~ you cant call it as $~
.
Sorry, I meant half-_beautiful_ (just kidding).
While this:
$myHash = @{ x = 1 y = $myHash.y }
would solve the problem of needing an automatic variable, I think it would introduce a maintenance headache: it's too easy to change the variable name later _only in the assignment_, forgetting that it must also be changed in the _entries_.
BTW, this is possible in YAML and some extensions of JSON.
Markup languages don't have a lot of options though. It's either force duplication or implement this. With PowerShell, you can already do this there just isn't any syntactic sugar for it.
One other solution that comes to mind is to allow usage of its own variable
Could break if folks are shadowing variables:
$that = @{ nah = 'test' }
$that = @{
one = $that.nah
}
Do you have some examples of existing projects or use cases that would greatly benefit from this?
The other variant - [HashTable($id)] @{ ... }
- looks more promising (perhaps without the $
, given that you're providing a variable _name_), because it would decouple the identifier used in the entries from the name of the receiving variable - but we'd also have to come up with a syntax for _ordered_ hashtables.
While we already have one pseudo type literal in the context of hash literals - [ordered]
- we should think about whether adding more is worth it.
it's too easy to change the variable name later _only in the assignment_, forgetting that it must also be changed in the _entries_.
Looks like a minor problem that can be minimized with some convention on variable name, like using all caps - If you change var with all caps, change it also in the HT body.
Do you have some examples of existing projects or use cases that would greatly benefit from this?
Yes I have, I keep my CICD configuration in gigant hashtable such as this one. I needed that feature on it multiple times.
Here is complete sample that could use this feature A LOT - actually, almost everything you see outside of HashTable is due to the this problem.
Here is another one.
Hash table with environment config
$sapwd = Get-Secret production\sa
$SqlServerUsers = [ordered] @{
Admin = 'sa', $sapwd
App = 'sa', $sapwd
}
$OsUsers = [ordered]@{
Admin = 'administrator', (Get-Secret production\administrator)
Deploy = Get-Credential deploy
}
$env = New-Environment @{
Name = $MyInvocation.MyCommand.Name.Replace('.ps1', '')
ServerEnvName = 'prod'
Description = 'CIR Production Environment'
db = @{
DbLocation = 'db-remote'
SqlServer = @{ Users = $SqlServerUsers }
OS = @{ Users = $OsUsers }
}
rest = @{
Port = 80
DeployType = 'IIS'
InstallDir = 'C:\inetpub\wwwroot'
WebApplicationName = 'CirRest'
OS = @{ Users = $OsUsers }
UseSsl = $true
}
reporting = @{
Port = 80
DeployType = 'IIS'
InstallDir = 'C:\inetpub\wwwroot'
WebApplicationName = 'CirReporting'
WebApplicationPoolName = 'CirReporting'
OS = @{ Users = $OsUsers }
UseSsl = $true
}
web = @{
Port = 80
InstallDir = 'C:\inetpub\wwwroot'
OS = @{ Users = $OsUsers }
}
docs = @{
RootPath = 'docs/user/'
OS = @{ Users = $OsUsers }
}
Metadata = @{ Notes = 'All roles are on separate servers except web and docs which share it' }
}
$env.docs.Server = $env.web.Server
$env.docs.Port = $env.web.Port
$env.docs.Server.ExternalHostname = $env.web.Server.ExternalHostname
$env
The other variant - [HashTable($id)] @{ ... } - looks more promising (perhaps without the $, given that you're providing a variable name), because it would decouple the identifier used in the entries from the name of the receiving variable - but we'd also have to come up with a syntax for ordered hashtables.
[ordered][Hashtable(id)]
?
HashTable is awesome for configuration and this would make it even more awesome.
_If_ something like this is implemented, I would consider a real (relative, e.g. .\foo
) _reference_ and _not a copy_, so that:
@{
foo = 'bar'
baz = 'foo' + $_
}
Besides, the way it is presented, it will only work for the interpreter (strings) knowing that hash table keys are of type [object]
(and not [string]
):
$Int = 1
$String = '1'
@{
$Int = 'Int'
$String = 'String'
baz = 'foo' + $_.1 # Which 1 ?
}
@majkinetor
Regarding your example: Note that you're referencing entries _across different_ hashtables, even though they're all _nested_ inside a single hashtable; e.g., $env.docs.Server = $env.web.Server
The beauty-half-full approach can handle this:
@{
web = ($web = @{ port = 80 })
docs = @{ port = $web.port }
}
However, an automatic-variable-based solution cannot, as such a variable could only refer to the _immediately enclosing_ hashtable.
Given that @SeeminglyScience has demonstrated that the $myHash = @{ foo=1; bar = $myHash.foo + 1 }
syntax would be a breaking change (and, as a moot aside, I wouldn't consider the renaming-the-variable-problem a minor one, I think it would become a source of many subtle bugs), that leaves us with the [Hashtable(id)]
syntax or similar.
A more concise form would be preferable, such as @[id]{ ... }
; e.g.:
@[main]{
foo = 1
bar = $main.foo + 1
web = @[web]{ port = 80 }
docs = @{ port = $web.port }
}
Unfortunately, @(id){...}
isn't an option because of the array-subexpression operator and neither is @id{...}
, because in argument-parsing mode the @id
part would be considered an instance of splatting; ditto for @:id{...}
, surprisingly.
_Some_ symbol that would currently result in a syntax error would be needed immediately after the @
; another option is
@<id>{ ... }
, but it isn't great either.
@iRon7
If a (pseudo) variable were made available, the usual rules would apply, just as with the variable-based workaround.
This includes disambiguating between string and numeric keys: $hash.1
gives you the [string]
entry, $hash[1]
the integer one.
My sense is that the primary use for this feature, especially in the context of configuration, would be in hashtables whose values are either .NET primitive types or strings, where effectively getting _copies_ at declaration time works as intended (but the syntax won't prevent you from creating cyclic references, just like using regular variables won't; similarly, if your values are .NET reference types, multiple entries can end up pointing to the same instance).
The System.Collections.Hashtable
/ System.Collections.Specialized.OrderedDictionary
types that PowerShell constructs from @{ ... }
/ [ordered] @{ ... }
literals don't allow for references _between entries_ (I assume that's what you meant). It isn't the role of such syntactic sugar to add capabilities to the underlying types.
baz = 'foo' + $_.1 # Which 1 ?
I was aware of this but it is minor limitation that can be documented or solved with convention that @mklement0 mentioned. Its completely not a reason to not implement it because of that. In case both string and numeric number exist as a key, one could be preferred (string probably).
However, an automatic-variable-based solution cannot, as such a variable could only refer to the immediately enclosing hashtable.
True and explicit names offered before solve it elegantly . This is basically the same as your _beauty-half-full approach_ but without 'half-full' :)
A more concise form would be preferable, such as @[id]{ ... }
I like this one a lot.
Seems like explicit naming is most bullet proof suggestion so far, without breaking compatibility.
Glad to hear it, @majkinetor, but there's trouble afoot: #5643 is coming back to life, and it proposes @[ ... ]
as the syntax to create lists.
That leaves @<id>{ ... }
or perhaps even @=id{ ... }
Given all this discussion, perhaps it makes sense if you closed this issue and opened a new one with a focused proposal that summarizes everything discussed so far.
Most helpful comment
Markup languages don't have a lot of options though. It's either force duplication or implement this. With PowerShell, you can already do this there just isn't any syntactic sugar for it.
Could break if folks are shadowing variables:
Do you have some examples of existing projects or use cases that would greatly benefit from this?