前回Excelで「関数型脳」を作る Vol.1:世界は書き換わらない —— 「代入」から「定義」へで、x = x + 1 という式への違和感から、関数型プログラミングでは「箱(変数)の中身を入れ替える(代入)のではなく、値につける名札(定義)をすること」を学びました。
今回は 「関数も値である」 という話をします。かっこよく言えば「学習の結果」ですが、実際は、ただのタイプミスからの気づきでした。
1. 偶然の発見:SUM関数に別名がつけられる
LET 関数を使って複雑な数式を書いていて、ざっくりこんな感じのコードを書いていました。
=LET(
_SUM, SUM, // ← ★ここ★
_SUM(1, 2, 3)
)
★印の行、本来なら _SUM, SUM(1, 2, 3) のように実引数を指定して合計値を入れるつもりだったのですが、何を血迷ったのか、名前定義のところに SUM と関数名だけを指定していました。
ですが、Enterキーを押したら、6 とセルに値が入り、_SUM が SUM 関数として機能しちゃったんです。
このことから、
-
x = 1(1という値に、xという名前をつける) -
_SUM = SUM(SUMという関数に、_SUMという名前をつける)
この2つに、何の違いもない 点に気づきました。
Excelでは、1 も "Hello" も、 SUM や VLOOKUP も、等しく 「名前を付けられるデータ(値)」 なんだと。
注意:全てのExcel関数に名前を付けられるわけではないです。
例えば、_L, LAMNBDAとかいても#NAME?エラーになります。
関数プログラミングに関する本を読んでいると、 「第一級関数(First-Class Functions)」 という話があり、いまいちピンとこなかったですが、この偶然のタイプミスによる気づきの方が自分としてはしっくりきました。
2. 「数値」も「関数」なのか?
関数プログラミングに関する本を読んでいて出てきたのが、アロンゾ・チャーチという数学者が考案した 「チャーチ数(Church Numerals)」 という概念です。
ざっくりいうと 「関数が値(データ)になるなら、逆に『値(データ)』だと思っていた『1』や『2』も、『関数』である」 というものです。
「1」とは、ある関数
fを、1回適用することである。
「2」とは、ある関数fを、2回適用することである。
つまり、「データとしての1」があるのではなく、「『1回やる』という行為(関数)そのものを『1』と呼ぼう」 というのです。
-
1→f(x) -
2→f(f(x)) -
3→f(f(f(x)))
「回数」という概念を、関数の「呼び出しの連鎖」で表現する。「数はモノではなく、アクション(関数)である」 という考え方です。
3. 実験:Excelで「関数としての2」を作る
この理論を、ExcelのLAMBDAで実装してみます。数値の 1 や 2 を使わず、関数だけで「数」を定義してみます。
定義としてはこんな感じ。
-
f: 何かしらの処理(関数) -
x: 最初の材料(値)
「イチ」の定義(Church 1)
「イチ」とは、「材料 x に、処理 f を 1回 適用する関数」のことです。
=LAMBDA(f, LAMBDA(x, f(x)))
「ニ」の定義(Church 2)
「ニ」とは、「材料 x に、処理 f を 2回 適用する関数」のことです。
=LAMBDA(f, LAMBDA(x, f(f(x))))
なんということでしょう!ここにはアラビア数字の 1 も 2 も出てきません。あるのは f を呼び出す構造だけです。これを LET で名前定義してみます。
=LET(
_CHURCH_1, LAMBDA(f, LAMBDA(x, f(x))),
_CHURCH_2, LAMBDA(f, LAMBDA(x, f(f(x)))),
"定義完了"
)
これで、Excelの中に「純粋な関数の世界」の 1 と 2 が生まれました。
4. 「関数としての数」を検算する
いやいや、「定義完了」しかセルに出てこないじゃん。
ここまでは、純粋な関数の定義をしただけなので、人間の目に見える数値にしてあげる(論理の世界から物理の世界にする)必要があります。
ここが関数型プログラミングのメンドクサイけどいいところだと思います。
論理と物理が明確に分かれているので。
物理は変更されるけど(例:ファイルからDBに変更)、論理はずっと一緒なのは美しい。
この「関数としての2」が本当に2回仕事をするのか確かめるために、分かりやすい「カウンター関数」を用意します。
-
カウンター関数: 入ってきた値に +1 する関数 (
inc) - 初期値: 0
もし _CHURCH_2 が本物の「2」なら、0 に +1 を2回適用して、結果は 2 になるはずです。
※実際にセルに入力するときは、エラーになるのでコメントを外してください。
=LET(
// 1. チャーチ数の定義(数値は使わない!)
_CHURCH_1, LAMBDA(f, LAMBDA(x, f(x))),
_CHURCH_2, LAMBDA(f, LAMBDA(x, f(f(x)))),
// 2. テスト用の道具(ここで初めて現実の数値を使う)
_Plus1, LAMBDA(n, n + 1), // +1する関数
_Start, 0, // スタート地点
// 3. 実験:「ニ」に「+1」と「0」を渡す
// 構造: _CHURCH_2( 足す関数 )( 初期値 )
_Result, _CHURCH_2(_Plus1)(_Start),
_Result
)
Enterキーを押すと、ちゃんと 2 になりました。
_CHURCH_2 という関数は、_Plus1 という関数を2回実行し、0 を 2 に変換しました。
実験:「足す関数と、数値」じゃなくて「文字を付け足す関数と文字列」を渡したらどうなる?
上記のCHARCH関数はそのままに、「文字列の末尾に『★』をつける関数」と、初期値「"Excel"」に差し替えてみました。
=LET(
_CHURCH_1, LAMBDA(f, LAMBDA(x, f(x))),
// チャーチ数の「ニ」
_CHURCH_2, LAMBDA(f, LAMBDA(x, f(f(x)))),
// 新しいテスト道具:文字列を連結する関数
_AddStar, LAMBDA(text, text & "★"),
_Start, "Excel",
// 実験:「ニ」に「★をつける関数」と「"Excel"」を渡す
_Result, _CHURCH_2(_AddStar)(_Start),
_Result
)
これを実行すると、セルには Excel★★ と表示されます。
※_CHURCH_1にすれば Excel★ になります。
チャーチ数の世界の「2」は、データではなく 「渡された関数を2回実行する(f(f(x)))」という振る舞い(アクション) なので、渡す関数(f)が「数値を足す関数」だろうが「文字列を連結する関数」だろうが、まったく気にする必要がないです( 関数も値と同じなので引数で渡せる )。
チャーチ数の「1」や「2」というのは、単なる数値ではなく、for 文や while 文のような 「繰り返し(ループ)処理」そのものを、関数だけで表現したもの に見えてきました。
Vol.4か5くらいでループの話をしたいと思います。
※数字が、実は「ループ」なのか?というのが面白いです。
5. まとめ:世界は関数でできていた
Excelのセルに入っている 1 や 2 という数字は、 命令型脳 の常識では「ただのデータ」ですが、 関数型脳 では、「1回適用する」「2回適用する」関数 という、なんでも関数で見る という癖が必要なんだなぁと。
- 命令型脳の常識: データ(値)と処理(関数)は別物。
- 関数型脳の常識: データ(値)と処理(関数)は一緒。
「関数を値として扱う」どころか、「値そのものも関数」です。
次回、 Excelで「関数型脳」を作る Vol.3:関数を生み出す工場 —— クロージャと部分適用では、「関数を生み出す工場 —— クロージャと部分適用」の話をします。
今回しれっと使った LAMBDA(f, LAMBDA(x, ...)) という関数の入れ子構造を使って、関数で関数を作る話をしていきます。
今日の気づき
-
SUMを変数に入れるタイプミスが、関数と値が一緒に扱えるという体験が目から鱗でした。 - 関数型プログラミングってメンドクサイことをやっていて、難解だなぁと思うのですが、打てば響くというか、関数をつつくと何か返ってくるという単純な仕組みの積み重ねで、全てをやろうとして、それができてしまうことが、実はすごいことなんじゃないのか?と思いました。
-
1や2は単なるデータではなく、「回数」という振る舞いを持った関数とみなせる。