List.Generateの使い方

Power Query歴、約3年の狸です。
@PowerBIxyz さんの[名記事](https://qiita.com/PowerBIxyz/items/ed6ebb547a9157561ad1)がある中で今更何を書くか、ということなんですが、ともかくも、基本的なところを書いてみました。
Power Queryでの関数の書き方なんか知らん、という方は実例に行く前に、[準備事項](#準備事項-関数の定義ができること)をお読みください。

#List.Generateでできること
##存在意義
Power Queryには、For文、Do ～ Loop文と言ったものがありません。
代わりに、いくつかの関数[^1]で、ループ処理を行うことができます。
その中でも、List.Generateは最も自由度が高い計算と言えます。[^2]


##戻り値
この関数の戻り値は、ループ各回の実行結果を全部を含んだリストです。
過程の結果が要らない場合は、リストの最後の要素を指定すればよいです。
例えば、`SomeList`というリストを得た場合に、下記のように書きます。

```
List.Last(SomeList)
もしくは
List.Reverse(SomeList){0}
```
###リストとは
Power Queryでいうリストというのは配列みたいなものです。
ただし、Power Queryでは一度、値を定義したら、その値を変更することができません。（immutable）
つまり、「リストaの中の値を変更する」ことはできないので、「リストaの中の値を変更して、リストbとして定義する」ことになります。

```pq:リストの例
a={1,3,5,7,9}　//要素が5つのリストにaという名前を付ける
a{0}　　　　　　//＝1
```

公式reference
リスト

https://docs.microsoft.com/en-us/powerquery-m/m-spec-values#list

不変性（immutability）

https://docs.microsoft.com/ja-jp/powerquery-m/m-spec-basic-concepts#immutability

##再帰との関係
Power Queryでは再帰関数の定義もできます。ただ、末尾再帰の最適化がされていないようです。
なので、全般、List.Generateで書いた方がよいと考えています。（今後は分かりませんが、今のところ）
※再帰関数を書く場合は、関数の名前の前に`@`をつける記法となります。
  詳しくは公式referenceを読んでください。この記事では触れません。

https://docs.microsoft.com/ja-jp/powerquery-m/understanding-power-query-m-functions

#準備事項 関数の定義ができること

最低限、Power Queryでの関数の定義ができないといけないです。
List.Generateを使う上では、引数0個と引数1個の2種類の関数を作れる必要があります。
##引数が1個の関数
###定義例
下記3つは同じ意味で、「引数に1を加えて返す」関数です。

- `(x)=> x + 1`
- `(_)=> _ + 1`
- `each _ + 1`


<details>
    <summary>実行した結果も確認したい方はこちらをクリック。</summary>
    <div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/16ef0cd6-7788-4093-2e81-597d32eb906b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/9bc7d63c-179d-da82-6d61-7e74b1214dbb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/acadbdc9-8c18-5398-c6e6-b731a8790b23.png)
</div></details>
###引数名に係る注意
上記の定義例で用いた`x`というのは引数につけた、ただの名前です。
あまり凝らずに、スペースなしの英語、アルファベットもしくは日本語を使うことをお勧めします。`a`とか`b`とか`apple`でも`banana`でも、`今日の昼飯`でもいいわけです。

※引数名の1字目に数字を使うとか、引数名の中に記号を使ったりすると、特殊な記法が必要になりますので、避けます。



##引数が0個の関数
やや引っかかりやすいのですが、引数0個の関数というものも定義できます。
1個の時の違って、書き方はこれ1通りです。
`each`と書くと、関数ではあっても、<font color="red">**引数0にはならない**</font>ので注意してください。

```
()=> 100　//OK：引数0の関数
each 100　//NG：引数1の関数！（引数を計算に使ってないだけ）
```
↓初期値をeachで書くとこうなります。「Expression.Error: 0 引数は、1 を必要とする関数に渡されました。」というエラーメッセ―ジが出ます。定義した関数1個の引数を必要とするが、何も渡されなかったから実行できず、エラーになったということです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/0553dd24-3383-8399-9fe1-5791137f2d3e.png)

※List.Generateでなくても、引数を渡さなければ同じエラーが出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/524eab77-5321-9301-2611-113115df4bec.png)


#例1：もっとも単純な例
関数の定義自体がよく分からない場合は、[準備事項　関数の定義ができること](#準備事項-関数の定義ができること)から読んでください。

##実例
List.Generateじゃなくてもできるじゃないか、ということは置いといて、簡単な計算を書いています。
なお、便宜的に各引数に日本語を当てていますが、説明の便宜上の呼び名で、公式なものではありません。

```
List.Generate(
                 ()=> 0,          //---初期値（initial）
                 (x)=> x <= 10,   //---継続条件（condition）
                 (x)=> x + 1      //---各回の処理（next）
            )
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/1f57dbab-24b6-18a2-1463-6fa44dcbcf3a.png)

##解説
###初期値（initial）
引数なしの関数を入れます。何も受けとる引数がないからです。だからといって、関数でない固定値を入れてしまうと、下記のようなエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/f9d2bf7a-7f93-33c3-e6aa-9ea8571f5acb.png)

###継続条件（condition）
次に生成しようとする値（予定値）を引数として受け取り、true/falseを返す関数を書きます。
trueである場合は予定値が値として確定し、次の予定値の計算に進みます。[^3]
falseの場合は予定値は捨てられ、その前までの値でリストが確定します。
<font color="red">**初期値も継続条件を満たさないといけない**</font>ことに注意してください。

###各回の処理（next）
確定済みの、現在の値を引数として受け取り、何らかの値を返す関数を書きます。

###実行順序
例1の場合は下記のように実行されます。

1. 「初期値」関数を実行して、予定値`0`を得る。
1. 予定値`0`を「継続条件」関数に入れ、真偽値として、`true`を得る。
1. 予定値`0`が確定値になる。
1. 確定値`0`を「各回の処理」関数に入れ、予定値`1`を得る。
1. 予定値`1`を「継続条件」関数に入れ、真偽値として、`true`を得る。
1. 予定値`1`が確定値になる。

以下略

#例2：よくある例
レコードを使えば、欲しい値と別の情報を持つことができ、書きやすくなります。
##レコードについて
レコード（record）というのはいわゆる連想配列のようなもので、名前の付いた値の集まりのことです。
名前と値の1セットをフィールドと呼びます。

```pq:recordの例
b=[年=2021,月=12,日=25]　//レコードにbという名前を付ける。改行も可能。
b[年]　　　　　　　　　　　//＝2021
```

公式reference（レコードについて）

https://docs.microsoft.com/en-us/powerquery-m/m-spec-values#record

##実例
レコードを使うと、計算内容をフィールドに分けることができるので、各回での実施内容を、複雑な計算を読みやすく記述することができます。

```
List.Generate(
    ()=> [i = 0, value = 1],     //初期値
    (x)=> x[i] <= 10,　　      　//継続条件
    //各回の処理
    (x)=> [
        i = x[i] + 1,
        value = x[value] + Number.Power(2,i)
    ],
    (x)=> x[value]      　　//最終出力値
)
```

<details>
    <summary>※実行結果（クリックで開きます）</summary>
    <div>
コードは縮めて書いています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/2d3ac8ef-1777-406b-5ed5-df26cdc975a1.png)

</div></details>

##実例の解説
基本的な3引数については例1で説明していますので、重複する部分は省きます。
###初期値（initial）
引数なしで、レコードを返す関数を入れます。
決まり、というより、レコード処理をする上で、後続の処理がレコードを前提としていますので、それと同じ構造でないと、計算できないためです。
###継続条件（condition）
引数として渡される予定値はレコードですので、今回は、レコードを引数に取る関数を書かないといけません。
`(x)=> x[i] <= 10`という関数は、引数`x`がレコードであり、その中のフィールド`i`について、10以下かどうかについてtrue/falseを返す、という意味です。
###各回の処理（next）
こちらも同様に、引数として渡される前回の確定値はレコードですので、レコードを引数に取る関数を書かないといけません。

```pq:例2の各回の処理（再掲）
(x)=> [
        i = x[i] + 1,
        value = x[value] + Number.Power(2,i)
    ]
```

フィールド`value`は、前回の確定値のフィールド`value`に2の累乗を足す計算をしています。2の累乗の指数として`i`（つまり、`x[i] + 1`）を指定しています。
`x[i]`と`i`を混同しないよう注意してください。

- `x[i]`：前回の確定値のフィールド`i`
- `i`：**今作成しようとしているレコードの**フィールド`i`


###最終出力値（selector）
今回は例1で紹介した3引数に加え、「最終出力値」という引数を使いました。レコードを使って書く場合は知っておくべき引数です。
今回のようにレコードの中の出力するフィールドを指定する、というのが主な使い方になります。
`(x)=> x[value]`とすることで、各回の確定値のレコードの中のフィールド`value`だけが戻り値となります。

なお、無い場合はエラーになるわけではなく、リストの要素となる戻り値がレコードになるだけです。一度試してみるとよいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/dac2fb19-6f79-5529-9c8b-e5a07789e1c4.png)


###実行順序
レコード値を使うと、計算の様子が例1とは異なってきます。
これはPower Queryで遅延評価（lazy evaluation）が行われることに起因する、と僕は解釈しています。

1. 「初期値」関数の中のフィールド`i`を計算して、予定値`0`を得る。
1. フィールド`i`の予定値`0`を「継続条件」関数に入れ、真偽値として、`true`を得る。
1. 0番目の値のフィールド`i`の確定値が`0`になる。
1. 「初期値」関数の中のフィールド`value`を計算して、0番目の値のフィールド`value`の確定値が`1`になる。
1. 0番目の確定値（レコード）を「各回の処理」関数に入れ、予定値`1`を得る。
1. 予定値`1`を「継続条件」関数に入れ、真偽値として、`true`を得る。
1. 予定値`1`が確定値になる。

#試した環境
- MS365のExcel：バージョン2109
- Power Queryのバージョン:2.97.321.0 64 ビット

#その他公式reference
https://docs.microsoft.com/ja-jp/powerquery-m/understanding-power-query-m-functions

https://docs.microsoft.com/ja-jp/powerquery-m/list-generate

https://docs.microsoft.com/ja-jp/powerquery-m/m-spec-lexical-structure#identifiers


[^1]: 他にはList.Accumulate関数、List.Transform関数があります。これらは計算対象のリストを引数とし、そのリストの各要素を引数として計算するfor each的な処理となります。また、Table.TransformRows関数、Table.TransformColumns関数なども、テーブルの各行、各列を処理するもので、ループ処理の一種と言えます。


[^2]: @PowerBIxyz さんのList.Generateの記事「[Power Query の List.Generate 関数ってなんだよー](https://qiita.com/PowerBIxyz/items/ed6ebb547a9157561ad1)」では、List.Accumulateとの比較もされています。

[^3]: ここでは各回で1個の値しか計算しない場合を前提とした説明になっています。[レコードの場合](#例2：よくある例)は、若干話が異なってきます。