Last time I got confused by the way PowerShell eagerly unrolls collections, Keith summarized its heuristic like so:
Putting the results (an array) within a grouping expression (or subexpression e.g. $()) makes it eligible again for unrolling.
I've taken that advice to heart, but still find myself unable to explain a few esoterica. In particular, the Format operator doesn't seem to play by the rules.
$lhs = "{0} {1}"
filter Identity { $_ }
filter Square { ($_, $_) }
filter Wrap { (,$_) }
filter SquareAndWrap { (,($_, $_)) }
$rhs = "a" | Square
# 1. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)
$rhs = "a" | Square | Wrap
# 2. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)
$rhs = "a" | SquareAndWrap
# 3. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)
$rhs = "a", "b" | SquareAndWrap
# 4. all succeed by coercing the inner array to the string "System.Object[]"
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)
"a" | Square | % {
# 5. all fail
$lhs -f $_
$lhs -f ($_)
$lhs -f @($_)
$lhs -f $($_)
}
"a", "b" | Square | % {
# 6. all fail
$lhs -f $_
$lhs -f ($_)
$lhs -f @($_)
$lhs -f $($_)
}
"a" | Square | Wrap | % {
# 7. all fail
$lhs -f $_
$lhs -f ($_)
$lhs -f @($_)
$lhs -f $($_)
}
"a", "b" | Square | Wrap | % {
# 8. all fail
$lhs -f $_
$lhs -f ($_)
$lhs -f @($_)
$lhs -f $($_)
}
"a" | SquareAndWrap | % {
# 9. only @() and $() succeed
$lhs -f $_
$lhs -f ($_)
$lhs -f @($_)
$lhs -f $($_)
}
"a", "b" | SquareAndWrap | % {
# 10. only $() succeeds
$lhs -f $_
$lhs -f ($_)
$lhs -f @($_)
$lhs -f $($_)
}
Applying the same patterns we saw in the previous question, it's clear why cases like #1 and #5 behave different: the pipeline operator signals the script engine to unroll another level, while the assignment operator does not. Put another way, everything that lies between two |'s is treated as a grouped expression, just as if it were inside ()'s.
# all of these output 2
("a" | Square).count # explicitly grouped
("a" | Square | measure).count # grouped by pipes
("a" | Square | Identity).count # pipe + ()
("a" | Square | Identity | measure).count # pipe + pipe
For the same reason, case #7 is no improvement over #5. Any attempt to add an extra Wrap will be immediately subverted by the extra pipe. Ditto #8 vs #6. A little frustrating, but I'm totally on board up to this point.
Remaining questions:
- Why doesn't case #3 suffer the same fate as #4? $rhs should hold the nested array (,("a", "a")) but its outer level is getting unrolled...somewhere...
- What's going on with the various grouping operators in #9-10? Why do they behave so erratically, and why are they needed at all?
- Why don't the failures in case #10 degrade gracefully like #4 does?