t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

【紹介記事】VBAで関数型言語

以下はmmYYmmddさんが開発したVBA関数型言語チックなことをやろうという野心的なライブラリである。
qiita.com

関数型言語を触ったことが無い方はいまいちその利点がピンと来ないかもしれないので簡単に説明しておく。

関数型言語とは、関数そのものを、文字列や整数といった普通の値と同じように扱える言語である。
これだけでは意味が分らないと思うので以下に例を挙げる。

まずVBAで適当に「計算」プロシージャを作成してみた。

Enum Ope
    p ' lus (足す)
    m 'inus (引く)
    t 'imes (掛ける)
    d 'ivide (割る)
End Enum

Sub Test単純VBA版()
    Call 計算(Ope.p, 1000, 20, 3, 9)
    Call 計算(Ope.m, 1000, 20, 3, 9)
    Call 計算(Ope.t, 1000, 20, 3, 9)
    Call 計算(Ope.d, 1000, 20, 3, 9)
End Sub

Sub 計算(Operation As Ope, ParamArray x())
    Total = x(0)
    For i = 1 To UBound(x)
        Select Case Operation
        '※本来ループの外で判定した方が処理効率が良いが、
        '今回はサンプルなのでコードの簡潔さを優先。
        Case Ope.p: Total = Total + x(i)
        Case Ope.m: Total = Total - x(i)
        Case Ope.t: Total = Total * x(i)
        Case Ope.d: Total = Total / x(i)
        End Select
    Next
    Debug.Print Total
End Sub

Ope列挙型を引数にとることで、どの種類の計算なのかをSelect文で判定させている。
演算子以外は類似」という処理が複数できてしまい、あまり効率的ではない。

では仮に、VBAが純粋な関数型言語だったらどうなるか。

こうなる。

Sub Test関数型への憧れ妄想版()
    '実際には動きません。
    Call 計算(+, 1000, 20, 3, 9)
    Call 計算(-, 1000, 20, 3, 9)
    Call 計算(*, 1000, 20, 3, 9)
    Call 計算(/, 1000, 20, 3, 9)
End Sub

Sub 計算(Ope, ParamArray x())
    Total = x(0)
    For i = 1 To UBound(x)
        Total = Total Ope x(i)
    Next
    Debug.Print Total
End Sub

上記のように、演算子そのものを引数として渡してしまえるため、
計算プロシージャのSelect文がいらなくなる。

残念ながらVBAではどうあがいても、ここまでスマートにはならない。

当然エラーになる。
f:id:t-hom:20150802044131p:plain

ただし、事前に演算子に相当する関数を作成しておけば、擬似的に引数とすることが出来る。

そういうことをするためのライブラリが、上記のVBAHaskellだと思う。

これを使ってコードを書くと、以下のとおり。

Sub TestVBAHaskell版()
    Call 計算(p_plus, 1000, 20, 3, 9)
    Call 計算(p_minus, 1000, 20, 3, 9)
    Call 計算(p_mult, 1000, 20, 3, 9)
    Call 計算(p_divide, 1000, 20, 3, 9)
End Sub

Sub 計算(Ope As Variant, ParamArray x())
    total = x(0)
    For i = 1 To UBound(x)
        total = applyFun2by2(Array(total, x(i)), Ope)
    Next
    Debug.Print total
End Sub

計算プロシージャに渡しているp_plusなどはあらかじめVBAHaskellに用意されている足し算関数(への参照ポインタ)である。
演算子の代わりとなる関数ポインタを計算プロシージャにOpe引数として渡す。

※最初に作ったTest単純VBA版のOpe列挙型とは関係なく、新規に引数として定義した変数なので注意

Ope関数に第一引数totalと第二引数x(i)を渡して結果を得たいが、
残念ながら、Opeは関数ではなく、関数への参照ポインタなのでOpe(total,x(i))とは書けない。
そこで、まず引数を配列として作成し、

Array(total, x(i))

applyFun2by2を使って関数を適用してやる。

すると最初に作ったTestプロシージャと同じ結果となる。

見た目は少々複雑になっているが、こちらの方が問題解決の方法としては優秀だ。
標準のVBA命令で作成した場合、演算子の違いだけで類似の処理を複数つくら無ければならない。
サンプルではFor文中の1カ所だけなのでそれほどメリットを感じないかもしれないが、これが複数箇所にまたがると似たような処理をあちこちに作成するハメになるので、メンテナンスの面からも非効率である。

同様の経験をされた方、もっとスマートに書けないか悩んでいる方は、試してみると良いと思う。
作者のmmYYmmddさんはQiitaの他にブログでも情報発信されている。
mmyymmdd.hatenablog.com


ただし、ある程度関数型言語の基本が理解できてからでないと、解説記事やコメントを見てもサッパリ分からないと思うので、map、fold、iotaあたりは押さえておいた方が良いかもしれない。

初めて関数型言語を勉強するなら、以下の言語がオススメである。

Gauche(ゴーシュ)

プログラミングGauche

プログラミングGauche

こちらは私が初めて本格的に関数型にトライした言語。
Scheme(スキーム)という言語の一種で、そのルーツはLISP(リスプ)である。
丸カッコだらけで見た目は変な言語だが、使いこなすとかなり柔軟な言語らしい。

関数型言語を学習するならまずSchemeがイチオシである。
Lispを初めその他の言語は利便性のためにいろんな機能を詰め込んでいるのに対し、Schemeはできるだけ言語をシンプルに維持するように作られているので学習に最適である。
Schemeの中でもGaucheは比較的色々機能を取り入れている方で実用的だと聞く。

ただマスターするに至らなかったので、私がGaucheで何か実用的なものが作れるようになった訳ではない。
それでも色々と勉強になることは多く、その後のプログラミングスタイルに多大な影響を与えてくれた言語だ。

これまで手続き型ばかりやっていた人にとっては、それはもう変な言語である。
まず足し算は1+2ではなく、(+ 1 2)と書く。
これはどうだろうか。

1 + 10 / 2 - 2

こうなる。

(- (+ 1 (/ 10 2)) 2)


関数型言語では、変数への「代入」とは言わず「束縛」と言うとか、一度「束縛」したら二度と値は変更できないとか、計算の実行ではなく、式の評価と言うとかで、全く手続き型とは文化が違う。
さらに、Schemeには手続き型言語の基本中の基本である、ループが存在せず、すべて再帰関数で書かれる。

再帰関数とは、自分自身を呼び出す関数である。
VBAで階乗を求めるプログラムを再帰関数で書いてみよう。

階乗とは、1からある数になるまで階段を登るみたいに1つずつ増やしながら掛けていくことで求まる数である。
例えば5の階乗は1*2*3*4*5=120である。

まずはループで書くとこうなる。

Sub test()
    Debug.Print Fact(5)
End Sub

Function Fact(n) As Integer
    Dim Sum As Integer
    Sum = 1
    For i = 1 To n
        Sum = Sum * i
    Next
    Fact = Sum
End Function

再帰を使うとこうなる。

Sub test()
    Debug.Print Fact(5)
End Sub

Function Fact(n) As Integer
    If n = 1 Then
        Fact = 1
    Else
        Fact = n * Fact(n - 1)
    End If
End Function

1ならそのまま返し、それ以外ならひとつnを減らしてFactを呼び出す。

Schemeはループが無いとプログラムなんて書けないという常識を壊してくれた。
何かと学びの多い言語である。

LISP(リスプ)

Land of Lisp

Land of Lisp

こちらは本屋で立ち読みしただけ。ただ本の雰囲気が楽しそうなのでいつか購入してトライしたいと思う。

それと、書籍ではないが、ノベルゲーム形式でLispを学べる以下のフリーソフトもオススメ。
lyrical.bugyo.tk
絵的にオタク趣味っぽくてオススメするのはちょっと恥ずかしいけど、内容はかなりマトモで、Lispがどんな言語なのか知りたい方はまずこちらを試してみて欲しい。
何を隠そう私が初めて関数型言語というのを知ったのもこのソフトである。
軽快な音楽とキャラの会話で楽しく学べるので、手軽に目から鱗体験をしたいならうってつけだと思う。

F#(エフシャープ)

F#はオブジェクト指向と関数型のマルチパラダイムが採用された言語だ。
Microsoftが作成した.Net Frameworkで動作する言語のひとつで、C#VB.Netなどと連携することもできる。

プログラミングF#

プログラミングF#

こちらはチュートリアルのように順に読み進められるのでオススメ。
ディスプレイが大きい方は、OreilyのサイトからPDFを買えるのでそちらがオススメ。
紙の本だとどうしても、コード入力の際に画面みたり下みたりするので首が痛くなる。

書画カメラとかあると良さげだけれど、試してないのでオススメとまでは言えない。

サンワサプライ USB書画カメラ CMS-V33SV

サンワサプライ USB書画カメラ CMS-V33SV

以下の本も高評価なので買ってみた。

実践 F# 関数型プログラミング入門

実践 F# 関数型プログラミング入門

こちらはリファレンスに近い感じがしたので、オライリーのプログラミングF#を買った上で2冊目にあると良いかなと思う。

これら以外のF#本は、肝心の関数型言語としての解説が薄いのでオススメできない。

その他

JavaVMで動作するScala(スカラ)なども良いかもしれないが未経験のため何とも言えない。
Haskell(ハスケル)はまだ手を出していないが、解説書が難しいイメージがあるので初心者へオススメというよりは、上記で物足りない方向けかなと思う。


以上

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。