Powershellでも(手軽に)enumerateしたい

## Abstract

Pythonでよく使う`enumerate`をPowershellで実装しました。

:::note
Pythonのenumerateは下記のようなものです。
```python
for i, elem in enumerate([1, 2, 3, 4, 5]):
    # `i`にindexが、`elem`に各要素が入っている
    print(i + elem)
```
:::

## 成果物

```powershell
function enumerate([parameter(mandatory)][scriptblock]$callback, [Parameter(ValueFromPipeline)][array]$iterable, [int]$start = 0) {
    <#
        .SYNOPSIS
            pythonic `enumerate` function.
        
        .DESCRIPTION
            This function iterates a given array-like object with the index number and its corresponding element.

        .PARAMETER callback
            A callback function or process which can take two arguments `($i, $element)`.
            In this callback function, $i and $_ can be used as a inedx and the i-th element, respectively.
        
        .PARAMETER iterable
            An array object. If no object is given, this function will try to obtain the value from `$input`, which is generated automatically in the context of `filter`.

        .PARAMETER start
            An index number that determines what number the enumerate loop starts with. defaults to `0`.
        
        .EXAMPLE
            C:\Users\takeMe1010> $temp = 1..5
            # the most simplest case like the `filter`
            C:\Users\takeMe1010> $temp | enumerate {Write-Output ($_ + $i)}
            1
            3
            5
            7
            9
        .EXAMPLE
            # the case like the `function`
            C:\Users\takeMe1010> enumerate {Write-Output ($_ + $i)} -iterable $temp
            1
            3
            5
            7
            9
        .EXAMPLE
            # with the anonymous function
            C:\Users\takeMe1010> $temp | enumerate {
            >>     param (
            >>         [Int32]$i,
            >>         [Int32]$value
            >>     )
            >>     Write-Output ($value + $i)
            >> }
            1
            3
            5
            7
            9
        .EXAMPLE
            # the index number starts with 2
            C:\Users\takeMe1010> enumerate {$i} -iterable @(1..5) 2
            2
            3
            4
            5
            6
    #>
    begin {
        # `System.Collections.ArrayList.Add` is faster than `+=`
        # ref: https://qiita.com/miyamiya/items/83127ae69d03f94d8443
        $processed = New-Object System.Collections.ArrayList
        $i = $start
    }

    process {
        # NOTE: `$iterable` is an array whose Count is 1 when called as a filter.
        foreach($element in $iterable) {
            $value = $callback.InvokeWithContext(
                $null,
                @(
                    [psvariable]::new('i', $i),
                    [psvariable]::new('_', $element)
                ),
                @($i, $iterable[$i])
            )
            if ($null -ne $value) {
                $processed.Add($value) | Out-Null;      # `System.Collections.ArrayList.Add` returns the index of the new item. By using `Out-Null`, its output is not shown in the shell.
            }
            $i += 1
        }
        if ($iterable.Count -eq 1) {
            # In the context of the filter, only the element currently processed is returned.
            $value;
        } else {
            # In the context of the function, the processed array is returned.
            $processed;
        }
    }

    end {
        
    }
}
```

## 使い方

:::note
インストール: 上記の関数をプロファイルに追加してください。
プロファイルのパスは`$PROFILE`に格納されています。

```powershell
echo $PROFILE
# C:\Users\takeMe1010\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
# 何らかのエディタで開いて、上記の関数を追加してください。
# e.g. `code $PROFILE`
```
:::

コールバック関数では`$i`にindexが、`$_`に各要素が入っています。
次の例のように、フィルターみたいに使えます。

```powershell
$temp = @(1..5)
$temp | enumerate {Write-Output ($_ + $i)}
#   1
#   3
#   5
#   7
#   9
```

関数みたいに使うこともできます。

```powershell
$temp = @(1..5)
enumerate {Write-Output ($_ + $i)} -iterable $temp
#   1
#   3
#   5
#   7
#   9
```

変数名を明示して無名関数(?)で受け取ることもできます。
この場合引数は必ず`(index, element)`の順番です。

```powershell
$temp = @(1..5)
$temp | enumerate {
    >>     param (
    >>         [Int32]$i,
    >>         [Int32]$value
    >>     )
    >>     Write-Output ($value + $i)
    >> }
#   1
#   3
#   5
#   7
#   9
```

## 参考文献

- インスパイア元記事。`InvokeWithContext`便利だ...

https://qiita.com/SilkyFowl/items/7bbf2576e8a3fef5f9a7#%E5%AE%9F%E8%A3%85%E3%81%AE%E3%81%93%E3%81%A0%E3%82%8F%E3%82%8A

- `+=`は遅いよという記事

https://qiita.com/miyamiya/items/83127ae69d03f94d8443

- 配列をループしながらindexを取り出す方法の議論例。できるだけ簡単にやる方法が見当たらなかったので作った。

https://stackoverflow.com/questions/1785474/get-index-of-current-item-in-a-powershell-loop