Excel の LAMBDA 関数（ベータ版）を試す

「Excel の数式がチューリング完全になった」でお馴染みの `LAMBDA` 関数。うちの環境でも使えたのでちょっと遊んでみました。

- [Excelの新機能「Lambda関数」によって「Excelの数式がチューリング完全になった」とナデラCEO。プログラミング言語としてのExcel数式であらゆる計算が可能に － Publickey](https://www.publickey1.jp/blog/21/excellambdaexcelceoexcel.html)
- [LAMBDA 関数 - Excel | Microsoft サポート](https://support.microsoft.com/ja-jp/office/lambda-%E9%96%A2%E6%95%B0-bd212d27-1cd1-4321-a34a-ccbf254b8b67) （公式）


# 導入

`LAMBDA` 関数は 2021-02-14 現在、Office365 サブスクリプションに加入して、Office Insider の「ベータチャネル」にサインアップすると使用可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/d2beee90-5919-9c20-1011-fe11aaab237b.png)

# 文法

`LAMBDA` 関数は **関数を返す関数** です。（引数なしでもイケますが）引数の名前を列挙していき、最終項を返す関数をつくることができます。

```javascript:LAMBDA関数の書式
=LAMBDA([arg1, arg2,...] calculation)
```

昨年末に正式導入された LET 関数とよく似た書式ですね。LET 関数ではローカル変数を宣言・初期化する必要があり、LET の中で完結していましたが、`LAMBDA` 関数では初期化せず、変数への代入は **呼び出し時** に行ないます。


```javascript:LET関数の書式
=LET(arg1, val1, [arg2, val2,...], calculation)
```

たとえば三角形の面積を求める関数を作ってみましょう。`LAMBDA` 関数の方は、エクセルでは見慣れない **カッコの連続** になっていることが分かります。「 **`LAMBDA` 関数は関数を返す** 」というのはこういう意味で、ここでは `LAMBDA(底辺,高さ, 底辺*高さ/2)` の部分が `SUM` や `AVERAGE` のような関数と同じような部品になっていて、呼び出すときにはカッコを伴って呼び出されるということです。

```javascript:三角形面積公式
// LET関数は内部に参照先を置く
=LET(底辺,A1, 高さ,B1, 底辺*高さ/2)

// LAMBDA関数は外部に参照先を置く
=LAMBDA(底辺,高さ, 底辺*高さ/2)(A1,B1)
```

これだけだと「そうなんだ……」という感じで、`LAMBDA` 関数単体ではあまり使いどころがありません。関数の定義と呼び出しを同じ箇所で行なっている **無名関数** の状態で、このままでは他のセルから呼び出すことができないからです。

# 関数の登録

ふつうのプログラミングをしている際の「 **関数を定義する** 」を行なうには、この `LAMBDA` 関数式を「登録」する必要があります。エクセルには「名前の登録」機能があり、いわゆるグローバル変数を定義することができますが、そこに `LAMBDA` 式を登録するのです。たとえば先ほどの関数を `DAIKEI_AREA` という名前で登録してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/4457b180-c3e6-7413-44f5-d8f9205b3559.png)

「数式」メニューから「名前の定義」を行ないます。「名前」欄には作成したい関数の名前、「参照範囲」に `LAMBDA` 関数式を入力して OK を押すと登録完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/9aba26d0-305b-0ae6-7fa5-75aeb835d8af.png)

こうすることでブックの任意の場所からユーザー定義関数を使えるようになります。オートコンプリートも効きますし、コメントも表示されます。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/3c36ce34-519e-cf1c-c593-984b9243258e.png)

普通の関数と同じような使用感です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/50e2e06c-f58b-c87c-2048-1b978dc8a906.png)

# 用法

公式は 4 つの例を挙げています。

- 華氏⇒セ氏の温度変換
    - こうした定数を含む公式を関数化しておけると便利ですね。

```javascript:ToCelsius
=LAMBDA(temp, (5/9) * (temp-32))
```

- 三角形の斜辺導出（ピタゴラスの定理）

```javascript:Hypotenuse
=LAMBDA(a, b, SQRT((a^2+b^2)))
```

- 語数カウント
    - 英語は分かち書きをするので、半角スペースの数から語数を算出していますね。

```javascript:CountWords
=LAMBDA(text, LEN(TRIM(text)) - LEN(SUBSTITUTE(TRIM(text), " ", "")) + 1)
```


- ある年の感謝祭の日付を表示
    - アメリカでは 11 月の第 4 木曜日が感謝祭にあたります。11/1 の曜日から第 4 木曜日の日付を逆算する感じですね。

```javascript:ThanksgivingDate
=LAMBDA(year, TEXT(DATE(year, 11, CHOOSE(WEEKDAY(DATE(year, 11, 1)), 26, 25, 24, 23, 22, 28, 27)), "mm/dd/yyyy"))
```

先ほども述べた通り、引数をとらない関数も作成できます。LET を併用するとさらにシンプルな記述ができるようになりますね。

```javascript:Greeting
=LAMBDA(LET(h, HOUR(NOW()),
        IFS(OR(h<=6, h>=23), "ｽﾔｧ……",
            h<=12,           "おはようございます！",
            h<=17,           "こんにちは！",
            TRUE,            "こんばんは！")))
// コメント：時刻に合ったあいさつを表示します。
```


## ループ表現

`LAMBDA` 式の中に自身の名前を入れ込むことで、再帰呼び出しを定義することができます。これは非常に意義深いことで、これによってループ処理を実装することができます。

Microsoft の Brian Jones はブログで以下のような例を挙げています。ある文字列 (testString) から、特定の文字たち (illegalChars) を除去したい、というタスクです。

|testString|illegalChars|(desired result)
|---|---|---|
|Brian Jones 206|1234567890|Brian Jones|
|excel85|abcdefghijklmnopqrstuvwxyz|85|
|River!@# On The Water|*!@#$%|River On The Water|

上のようなデータセットが与えられたとき、ふつうのプログラミングなら（正規表現で置換するか）illegalChars でループを回して文字数分だけ置換を繰り返すと思いますが、従来の Excel 数式でこのロジックを書くのは非常に骨でした。ループが存在しなかったからです。

`LAMBDA` 内 `LAMBDA` を用いて、illegalChars の一文字目を削りながら再帰的に値を渡していくことで、ループを実現することができます（インデント等は引用者が変更）。

```javascript:ReplaceChars
=LAMBDA(textString, illegalChars,
        IF(illegalChars="", textstring,
           REPLACECHARS(SUBSTITUTE(textString, LEFT(illegalChars, 1), ""), 
                        RIGHT(illegalChars, LEN(illegalChars)-1)
)))
```


- 参考：[Announcing LAMBDA: Turn Excel formulas into custom functions | Brian Jones (OFFICE) Microsoft](https://techcommunity.microsoft.com/t5/excel-blog/announcing-lambda-turn-excel-formulas-into-custom-functions/ba-p/1925546)

この仕組みを使えば、階乗や FizzBuzz も簡単に自作できます。

```javascript:KAIJO
=LAMBDA(num, 
        IF(num=1, 1,
           num * KAIJO(num-1)))
// コメント：与えられた自然数の階乗を返します。
```

```javascript:FIZZ_BUZZ
=LAMBDA(num,
        IF(num=1,            TEXT(1,"0"),
           IF(MOD(num,15)=0, TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),"FizzBuzz"),
           IF(MOD(num,5) =0, TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),"Buzz"),
           IF(MOD(num,3) =0, TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),"Fizz"),
                             TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),TEXT(num,"0")))))))
// コメント：与えられた自然数まで FizzBuzz を出力します。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/4dfe3b90-ab36-c5dc-3a6f-b402deb12c21.png)

もっとも、この程度であれば出力方法によっては再帰を使わず表現可能です。たとえば FizzBuzz についても、`SEQUENCE` 関数で配列を作成して入力として利用することで、疑似的にループを実行することができます。

```javascript:FIZZ_BUZZ2
=LAMBDA(num,
        LET(index, SEQUENCE(1,num),
        IFS(index=1,         TEXT(1,"0"),
            MOD(index,15)=0, "FizzBuzz",
            MOD(index,5) =0, "Buzz",
            MOD(index,3) =0, "Fizz",
            TRUE,            TEXT(index,"0"))))
// コメント：与えられた自然数まで FizzBuzz を出力します（スピル）。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/cc375b62-b41b-2e0e-5b0c-082b6ddd83a4.png)

# ローカル関数

`LAMBDA` 関数の少し前に LET 関数というものが導入され、私も使い方について少し記事を書きました。

- [Excel の LET 関数を試す | Qiita](https://qiita.com/a_eau_/items/0a59a70946dd40672ddc)

LET 関数はブロックを作り、その中でローカル変数を定義することを可能にします。実は `LAMBDA` も LET 内で定義可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/e8db6673-574f-57bf-6aea-f45afc908343.png)

もちろん配列入力もできます。この機能によってネ申エクセルはさらに高次元へと押し上げられるでしょう。

## ローカル関数での再帰について

2021/04/18 追記：以前の版では以下のような例を挙げて、`LET` 内でも再帰的な `LAMBDA` 定義が可能であると記述していました。

```javascript:誤った例
=LET(
    x,   123,
    y,   84,
    GCD, LAMBDA(a, b, IF(b=0, a, GCD(b,MOD(a,b)))),
    res, GCD(x,y),
    res
)
```

ところが、コメントで @lensouko さんから以下の指摘をいただきました。

>ローカル関数の再帰は使えないですね
例に上がっているGCDは既存の関数なので使えているだけみたいですね
再帰で使いたい場合は再帰関数を定義してから使用するしかないようです

これは `GCD` が既存関数であることを忘れていた筆者の確認不足でした。上の「誤った例」では、**4 行目の右辺で既存の `GCD` 関数が呼ばれており、それが左辺のローカル変数 `GCD` に代入されている** という挙動になります（既存関数の名前をローカル変数が「上書き」することは可能）。つまり、実は「誤った例」では再帰の１層目だけしか定義できていなくて、それ以降の計算は既存の `GCD` 関数が担っていたから、見かけ上正しく動いていたわけですね。

というわけで、既存関数に名前が被っていない `KAIJO` だと普通に名前解決に失敗して `#NAME?` を吐きます。

```javascript:名前解決できない
=LET(
    x,   5,
    KAIJO, LAMBDA(num, IF(num=1, 1, num * KAIJO(num-1))),
    res, KAIJO(x),
    res
)
```

2021/06/26 追記： @ytaki0801 さんにローカルで再帰関数を定義する方法を教えていただいたので、記事にしました。

- [Excel の LAMBDA 関数を試す (2)](https://qiita.com/a_eau_/items/4340f01ae1b4f8e6c5fa)

# カリー化

複数の引数をとる関数の一部を埋めて（部分適用）、引数の個数を少なくすることができます。たとえば自然言語の二項述語を定義したあとで、目的語のみ固定した一項述語を定義することができます。この場合 `LAMBDA` は 2 引数をとる関数から 1 引数をとる関数を返しています。

```javascript:カリー化
LOVES
=LAMBDA(sbj, obj, CONCAT(sbj, " loves ", obj, "."))
// =LOVES("Jiro", "Saburo") とすると Jiro loves Saburo. を返す

LOVES_TARO
=LAMBDA(sbj, LOVES(sbj, "Taro"))
// =LOVES_TARO("Kanako") とすると Kanako loves Taro. を返す

HANAKO_LOVES
=LAMBDA(obj, LOVES("Hanako", obj))
// =HANAKO_LOVES("Kana") とすると Hanako loves Kana. を返す

YUKI_LOVES_TARO
=LAMBDA(LOVES_TARO("Yuki"))
// =YUKI_LOVES_TARO() とすると Yuki loves Taro. を返す
```

# 高階関数

これまでの例から察されるとおり、関数は別の変数に格納することができるだけでなく、関数の引数に与えることもできます。ただし **既存の Excel 関数** を渡したい場合は一度 LAMBDA を介する必要があるようです。

以下のように、第一引数の値に対して第二引数の関数を適用する `MAP` 関数を考えてみましょう。定義自体は問題なくできますし、`LAMBDA` で別途登録したユーザー定義関数を渡すと想定通りに動きますが、既存の Excel 関数の名前を与えても動きません。冗長ですが、いったん `LAMBDA` を挟んで関数であることを「分からせ」てやる必要があります。

```javascript:高階関数
MAP
=LAMBDA(array, func, func(array))
// 1~10 の配列を与えて、各数値の階乗を出力したいとき、
// =MAP(SEQUENCE(1,10), FACT) だと動かない
// =MAP(SEQUENCE(1,10), LAMBDA(x, FACT(x))) だと動く
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/3a074440-9db5-3b64-b817-672643e20d8c.png)




# まとめ

- `LAMBDA` 関数でユーザー定義関数を作れる。
- 再帰的呼び出しが可能になるので、ループを表現できる。
- `LET` ブロックと組み合わせることで、構造化プログラミングができる。
- 無名関数、カリー化、高階関数が実現可能で、関数型プログラミングができる。