これは私に関数型言語の素晴らしさを教えてくれた本である。

- 作者:Kahuaプロジェクト,川合史朗
- 出版社/メーカー:オライリージャパン
- 発売日: 2008/03/14
- メディア:大型本
- 購入: 22人 クリック: 713回
- この商品を含むブログ (244件) を見る
プログラミングGauche(ゴーシュ)。
一度売ってしまったのだけど、どうしてもその本のコラム「Lisp脳」の謎に迫る-Schemeプログラマの発想がもう一度読みたくなって先日ついに買い戻してしまった。
そのコラムは手続き型の思考と関数型の思考の違いについて説明したもので、初めて読んだときはあまりに考え方が違うので衝撃を受けた。
これはGaucheという言語※の本なので、コラムのコードも当然Gaucheで書かれているのだが、今回はこのコラムの内容をVBAのコードに置き換えて紹介ようと思う。
※厳密にいうとGaucheは処理系の名称なので、言語名はSchemeだけど、どういうことか説明しだすとややこしいので割愛。
さて、その前に関数型言語について。
関数型言語とは
関数型言語というのは、ざっくりいうと関数を値として扱うことができる言語である。値であるから、変数に入れたり、ほかの関数の引数にしたり、戻り値にしたりすることができる。
関数というとExcel関数をイメージしてしまうかもしれないけど、ここではひとまずVBAのプロシージャに相当するものと考えてほしい。
つまり、プロシージャを変数に入れたり、他のプロシージャに引き渡したり、あるプロシージャの戻り値としてプロシージャを返したりできるのだ。
コラムの内容
このコラムではコードの題材はとしてFizzBuzzを扱っている。そこでまず、FizzBuzzのルールを説明する。
FizzBuzzのルール
手続き型の思考
手続き型の思考では、与えられた課題をどのように実現するか、つまりHowを考え、その通りにプログラムを作成する。
- まずForループで変数 i を1~100まで順番に増やす。(How)
- ループの中で、3で割り切れるか、5で割り切れるか、3と5で割り切れるかをチェックし、それぞれ対応する結果を出力する。
図で書くと、このようなフローチャートのイメージになる。
VBAで関数型の思考法をコードにしてみる
先ほどの関数型の考え方を図にすると、このようなイメージになる。
左上の1~100と書かれているものは、1~100が格納されたリストである。今回はVBAプログラマ向けの説明なので、1~100が配列に順番に格納されているイメージで良いかと思う。関数型言語にはこのリストを瞬時に作るIota(イオタ)関数というものがある。
VBAに同等のものは存在しないので、自分で作ってみた。
Function Iota(n)Dim ret: ReDim ret(1To n)Dim i For i =1To n ret(i)= i Next Iota = ret EndFunction
次にFizzBuzz関数と書かれているものは、数値をひとつだけ受け取って、結果(Fizz、Buzz、FizzBuzz、元の数値のいずれか)を返す関数だ。
VBAではこのようになる。これは関数型言語でも自分でコーディングする部分だ。
Function FizzBuzz(n)Dim ret SelectCase0Case n Mod15: ret ="FizzBuzz"Case n Mod5: ret ="Buzz"Case n Mod3: ret ="Fizz"CaseElse: ret =CStr(n)EndSelect FizzBuzz = ret EndFunction
さて、1つだけ数値を受け取るFizzBuzz関数に、複数の数値が集合した配列をそのまま渡すことはできない。
ここで登場するのがMapという関数。関数型言語には大抵、Map関数があり、リストと関数を引数として渡すと、リストのそれぞれの要素に関数を適用して、戻り値として新しいリストを返してくれる。
※ちなみにMapは一般的には地図と訳すけど、ここでは「対応付ける」という意味。
VBAにはMap関数がないので作ることになるが、VBAは関数型言語ではないため関数を引数として渡すことができない。
そこで関数名で実行できるApplication.Runを利用した擬似Map関数を作ってみた。これで関数名を文字列として渡すことができる。
Function Map(function_name AsString, list)Dim ret: ReDim ret(LBound(list)ToUBound(list))Dim i For i =LBound(list)ToUBound(list) ret(i)= Application.Run(function_name, list(i))Next Map = ret EndFunction
これで結果配列を得られるようになったので、あとは出力だけ。
関数型言語にはリストをそのまま出力する関数が備わっているが、VBAには無いので配列をすべて出力するプロシージャを新たに作る。
Sub PrintList(list)Dim i For i =LBound(list)ToUBound(list) Debug.Print list(i)Next Debug.PrintEndSub
そしてすべてをつなぐメインコードはこうなる。
Sub Main()Call PrintList(Map("FizzBuzz", Iota(100)))EndSub
Iotaで作られた配列にFizzBuzz関数がマップされ、その結果をプリントするというコードになっている。
今回はVBAなので、Iota関数、Map関数、PrintListプロシージャでそれぞれループを使用しているが、関数型言語ではこれらの関数はあらかじめ用意されているので実質ループは必要ない。
材料である1~100のリストはIotaでポンと作れるし、そこに自作の関数をMapさせてはい出来上がりという世界だ。関数型言語なら、実際のコーディングはFizzBuzz関数と、メインコードのみで済む。