James Kolpack
James Kolpack

Reputation: 9382

PowerShell should be returning object[] from pipe?

Given this function:

> function Get-ArrayOfArrays() {
    (1,2),(3,4) | % { $_ }
}

I'd expect the return type to be an array of arrays. However, it appears that the inner arrays are being expanded into the pipe:

> Get-ArrayOfArrays | % { $_.GetType().Name }
Int32
Int32
Int32
Int32

In fact, running the pipe inline will produce these results:

> (1,2),(3,4) | % { $_ }
Object[]
Object[]

I've found that if I use the return keyword inside the foreach, I can achieve the desired result:

> function Get-ArrayOfArraysWithReturn() {
    (1,2),(3,4) | % { return $_ }
}

> Get-ArrayOfArraysWithReturn | % { $_.GetType().Name }
Object[]
Object[]

This behavior seems to be "If returning from a function with uncaptured objects, and those objects happen to be arrays, then expand them". Is this one of those "do-what-is-useful-most-of-the-time" design decisions? Or am I missing something else?

Upvotes: 3

Views: 595

Answers (2)

Tahir Hassan
Tahir Hassan

Reputation: 5817

I was going to ask a similar question, namely "How do you prevent PowerShell from unrolling /joining / concatenating arrays?".

What I found out is that the interactive interpreter does an extra step of unrolling before it prints the results in the console:

For example

> (1, 2), (3, 4)
1
2
3
4

Which means it was already unrolled once:

[ 1, 2 ] ++ [ 3, 4 ] == [ 1, 2, 3, 4 ] 

Therefore what the interpreter prints is not necessary the real output of the code.

For example, the following code

1, 2, 3 | ForEach-Object { $_, ($_ * 2) }

takes the first three integers and gives us a list of two numbers [ x, x * 2 ] for each one. The result is:

[ 1, 2, 2, 4, 3, 6 ]

However I wanted the following output:

[ [ 1, 2 ], [ 2, 4 ], [ 3, 6 ] ]

I used the comma , operator as Keith Hill suggested:

$list = 1, 2, 3 | ForEach-Object { , ($_, ($_ * 2)) }

Since we returning an array of, array of, int's for each input:

1   -> [ [ 1, 2 ] ]
2   -> [ [ 2, 4 ] ]    
3   -> [ [ 3, 6 ] ] 

We expect it to be unrolled once only so that $list equals:

[ [ 1, 2 ] ] + [ [ 2, 4 ] ] + [ [ 3, 6 ] ] == [ [ 1, 2 ], [ 2, 4 ], [ 3, 6 ] ]

And we can test that it is

> $list[0]   #  get first element of list, should be [ 1, 2 ]
1
2
> $list[2][1]  # from the third elem, [ 3, 6 ], get 2nd elem, should be 6
6

However, when the interpreter prints the list, it unrolls it once more:

[ 1, 2 ] ++ [ 2, 4 ] ++ [ 3, 6 ] == [ 1, 2, 2, 4, 3, 6 ]

so we get

> $list
# prints [ 1, 2, 2, 4, 3, 6 ]

Upvotes: 0

Keith Hill
Keith Hill

Reputation: 201612

(1,2),(3,4) | % {$_}

results in your function outputting (1,2) and (3,4) - the same as this function would:

function foo { 
    1,2
    3,4 
}

The kicker is that each of these arrays have their content's unrolled (flattened) again as part of the process of a function outputting objects e.g.:

PS> foo | %{$_.GetType().Name}
Int32
Int32
Int32
Int32

This is similar to what you see on the host when you execute this:

PS> 1,2
1
2

The array is unrolled such that each element of the array is displayed. The following should do what you want:

function Get-ArrayOfArrays() { 
    (1,2),(3,4)
}

When (1,2),(3,4) hits the function output it is unrolled once, outputting (1,2) and (3,4). Another technique you can use to work-around collection unrolling is to wrap a collection within another single element collection using the , operator to preserve the shape of the original collection e.g.:

PS> function foo { ,((1,2),(3,4)) }
PS> foo | gtn
Object[]

Upvotes: 4

Related Questions