しばたテックブログ

気分で書いている技術ブログです。

PowerShellにおける"配列リテラル"について

PowerShell Advent Calendar 2017、9日目です。

qiita.com

久しぶりのPowerShellの基本がわかっていなかったシリーズです。

以前のオープンソースカンファレンス2017 Hokkaidoでの登壇などで、ふつうに

PowerShellには配列リテラルがあります。

と説明していたのですが思いっきり間違っていました。
誤った説明をしてきたことを謝罪するとともに、あらためてPowerShellにおける"配列リテラル"について説明したいと思います。

PowerShellの仕様としての"配列リテラル"

中の人であるBruce Payetteさんが書いたWindows PowerShell in ActionPowerShell Blogの記事によれば、


  • Windows PowerShell in Action : 2.4 Collections: arrays and sequencesより一部引用

Here’s how array literals are defined in PowerShell: They’re not. There’s no array literal notation in PowerShell.


  • PowerShell Blog : Array Literals In PowerShell より一部引用

The first thing to understand is that there are no array literals in PowerShell Arrays are built using operators or casts.


と、PowerShellに"配列リテラル"は存在しないと説明されています。

例えば、

$a = 1,2

$a = (1,2)

といった例における配列は両者ともリテラルではなくカンマ演算子によって生成されるものであるとされています。

また、PowerShellの言語仕様書(本エントリではV3を例示)においても、2.3.5.5 Array literalsにおいて、

2.3.5.5 Array literals

PowerShell allows expressions of array type (§9) to be written using the unary comma operator (§7.2.1), array-expression (§7.1.7), the binary comma operator (§7.3), and the range operator (§7.4).

との説明がなされ、仕様として"配列リテラル"は存在しないことがきちんと説明されていました。

PowerShellの構文から見た"配列リテラル"

私が間違った説明をした原因とも言えるのですが、PowerShellの構文上、ASTにおいては"配列リテラル"に類するものが存在しています。

先述の配列の例をShowPSAstモジュールを使いASTを可視化してみると以下の様になります。

Import-Module ShowPSAst
{ $a = (1,2) } | Show-Ast

f:id:stknohg:20171207181148p:plain

1,2の部分についてはASTとしてはArrayLiteralAstとなっておりあたかも配列リテラルを取り扱うかの様になっています。

そして、言語仕様書ではこれはarray-literal-expressionとして以下の様に定義されています。

f:id:stknohg:20171207181206p:plain

図の上部を見ればわかりますが、これはarray-literal-expressionといいつつカンマ演算子専用の構文です。

この仕様と実装のちぐはぐさが私の誤解の根本的な原因となりました。

ちなみに、仕様の説明では"二項"カンマ演算子と記載されていますが、単項や3項以上の場合でもこのArrayLiteralAstが使われます。

# 単項の場合
{ $a = ,1 } | Show-Ast

f:id:stknohg:20171207181228p:plain

# 3項以上の場合
{ $a = 1,2,3,4 } | Show-Ast

f:id:stknohg:20171207181245p:plain

【補足】配列部分式演算子について

補足としてもう一つややこしい話をします。

PowerShellにおいて配列を生成するにはカンマ演算子のほかに@()による配列部分式演算子を使うことができます。

ここでカンマ演算子と配列部分式演算子の違いについて触れておきます。

配列部分式演算子は構文としてはarray-expressionとして以下の様に定義されています。

f:id:stknohg:20171207181259p:plain

この演算子最大の特徴は()の中がstatement-listと複数の文を受け入れことができる点になります。

この説明だけみればカンマ演算子と配列部分式演算子が全然別物だと認識できる*1かと思いますが、ちょっとややこしい例を挙げてみます。

#
# カンマ演算子による配列生成
#
# 定義可能
$array01 = (
    1,
    2,
    3
)
# エラーになる
$error01 = (
    1
   ,2
   ,3
)
# エラーになる
$error02 = (
    1
    2
    3
)

#
# 配列部分式演算子による配列生成
#
# 定義可能
$array10 = @(
    1,
    2,
    3
)
# 定義可能
$array11 = @(
    1
   ,2
   ,3
)
# 定義可能
$array12 = @(
    1
    2
    3
)

この6つの配列定義についてそれぞれ定義可・不可となる理由がわかるでしょうか?
これがきちんと説明できればPowerShellの配列をマスターしたも同然です。

カンマ演算子による配列生成の解説

カンマ演算子による配列生成で$array01以外がエラーとなるのはarray-literal-expressionの定義にある通り、カンマ演算子を使い複数行にわたる配列定義を行う際のカンマは後置のみ許可されているためです。

こちらは割と直感的に理解できるのではないかと思います。

配列部分式演算子による配列生成の解説

そして、ちょっと厄介なのが$array10$array12で、これらはすべて定義可能であり同じ結果を返します。

まず、$array10

$array01 = (
    1,
    2,
    3
)

$array10 = @($array01)

と同等のイメージになります。これはわかりやすいかと思います。

つぎに$array11$array12については以下の様に;を入れるとわかりやすくなります。
本項の最初に説明した()の中に複数の文が来るケースに該当します。

# 定義可能
$array11 = @(
    1;
   ,2;
   ,3;
)
# 定義可能
$array12 = @(
    1;
    2;
    3;
)

それぞれに注釈を入れると

# 定義可能
$array11 = @(
    1;    # 文1 : 数値リテラル -> パイプライン
   ,2;    # 文2 : 単項カンマ演算子 + 数値リテラル -> パイプライン
   ,3;    # 文3 : 単項カンマ演算子 + 数値リテラル -> パイプライン
)
# 定義可能
$array12 = @(
    1;    # 文1 : 数値リテラル -> パイプライン
    2;    # 文2 : 数値リテラル -> パイプライン
    3;    # 文3 : 数値リテラル -> パイプライン
)

と、$array11は単項カンマ演算子を使った3つの文、$array12は数値リテラルを使った3つの文から構成されており、3つの文を評価した結果が配列化されることになります。

本エントリではこれ以上触れませんがそれぞれのケースに対してASTを見てみるとより分かりやすいでしょう。

最後に

とりあえずこんな感じです。

個人的にPowerShellの配列は自由度の高さとその代償としての厄介さを抱えているのかなと思っています。

追記

ぎたぱそ先生の配列生成のパフォーマンスについての記事で本エントリを取り上げてもらいました。

tech.guitarrapc.com

とても有用な内容ですので是非こちらもご覧ください。

*1:実際別物です