VBAHaskellの紹介 その2 (合成関数)

続いて関数合成について。

VBAでももちろん関数を合成して計算することはできるが、関数型言語のように合成関数自体を変数に入れたり関数から出力したりすることはできない。

例えばLog(0.1 + Abs(x-5))という計算を多数のxに対して行う場合、普通はこの式を埋め込んだユーザー定義関数を作る。

しかしVBAでもラムダ式もしくはファンクタのようなものを作れれば便利だし、ぜひともそれを前回紹介したmapFに渡せるようにしたい。そうしないとライブラリに渡すためのヘルパ関数をその都度作るハメになるからだ。

そこでパーツを適当に組み合わせてファンクタを作れるようにした。

 a = p_log(p_plus(0.1, p_abs(p_minus(, 5))))

これはC++風に書くと下式に相当する。
  auto  a = [](auto x) { return Log(0.1 + Abs(x-5)); }

Haskellだとこう(?)
  a = \x -> log(0.1 + abs(x-5))

パーツである p_log, p_plus, p_abs, p_minus はあらかじめ用意*1してあり、それぞれはVBA上の関数(のポインタ的なもの)である。

a に対して、mapF(a, iota(1, 10)) とか applyFun(4, a) といった式で実際に計算させることができる。

 残念ながら表現力には限界があって、演算子を組み合わせてもっと自然にこう書くことはできない。

  a = p_log(0.1 + p_abs(p_minus(, 5)))  ⇐ ダメ

 

VBAHaskellの紹介 その1 (最初はmapF) - mmYYmmdd’s blog

*1:Haskell_2_stdFunモジュールで定義

VBAHaskellの紹介 その1 (最初はmapF)

VBAHaskellHaskellを真似たVBAのライブラリである。

リスト処理系の関数や関数合成などの機能によって退屈な繰り返し処理を減らし、VBAをより面白くするのが目的だ。

その中でもmapFはコアとなる関数で、リンクしたgithubにあるHaskell_1_Coreモジュールの中で定義されている。*1
コールバック関数と配列を引数に取り、Haskellのmap関数と同様の動きをする。

リンクしたテストモジュール(test_module.bas)にあるデモ関数 vbaUnit を見ていただきたい。最初の7行はmapFのサンプルであり、実行すると以下のように表示される。*2

 

1 :    ------- mapF ----------

2 :    mapF(p_log, Array(1, 2, 3, 4, 5, 6, 7))
3 :       0 0.693147 1.098612 1.386294 1.609437 1.791759 1.945910
4 :    mapF(p_minus(3), iota(1,15))
5 :       2 1 0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12
6 :    mapF(p_minus(, 3), iota(1, 15))
7 :       -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12
8 :

2. の第1引数であるp_logは対数関数(logN)を指す一種の関数ポインタ。logNは2変数関数だが、第2引数は省略可能なので実質的に1引数関数と考えることができる。(省略時は自然対数

結果はHaskellのmapと同様、列 [1, 2, 3, 4, 5, 6, 7]の各要素に対するLogの値が3行目に出力されている。

これが2変数関数だったらどうか。
減算関数であるminusのポインタp_minusを使う例が4. と6. である。
p_minusに引数を与えてp_minus(3), p_minus(, 3)となっているが、これらはそれぞれ1番目の引数と2番目の引数を特定の値に束縛したものである。

C++で言えば std::bind1st および std::bind2nd に相当する。 

4. では第1引数を3に束縛しているので
3-1, 3-2, 3-3, 3-4, 3-5, 3-6, 3-7, 3-8, 3-9, 3-10, 3-11, 3-12, 3-13, 3-14, 3-15
が、6. では第2引数を3に束縛しているので、
1-3, 2-3, 3-3, 4-3, 5-3, 6-3, 7-3, 8-3, 9-3, 10-3, 11-3, 12-3, 13-3, 14-3, 15-3
の計算結果が出力されている。*3

mapFに限らず、コールバックとして渡せる関数のシグネチャパターンは1種類しかないが、片方の引数を束縛することができる。普通にネストすることによる合成関数の作成や、関数の列をfoldlやfoldr系の関数に渡すことによって任意個数の関数の合成などもできるようにした。

dllを必要とするのが欠点だが、発展性のある面白いライブラリになることを期待しているので、リンクしたソースコードをダウンロードして遊んでもらえると嬉しい。

 

VBAHaskellの紹介 その2 (合成関数) - mmYYmmdd’s blog

ソースコード

github.com

 dllのバイナリ:

http://home.b07.itscom.net/m-yamada/VBA/mapM.dll

http://home.b07.itscom.net/m-yamada/VBA/mapM64.dll

 

 上記のうち、動かすのに必要なものは以下の6つのVBAモジュールとdllバイナリ。
C++のファイルは自分でコンパイル&ビルドしない場合は不要 *4
Haskell_0_declare.bas
Haskell_1_Core.bas
Haskell_2_stdFun.bas
Haskell_3_printM.bas
Haskell_4_vector.bas
Haskell_5_sort.bas
Haskell_6_iterator.bas
test_module.bas(テスト用モジュール)
misc_*.bas(テストモジュールで使用)
mapM.dll (32bit Office or 64bit Office)

basファイルはインポート*5すればいいが、Haskell_0_declare.bas と misc_random.bas にはAPI関数のDeclare文を記載しているので、dllはパスが通っているフォルダに置くかまたは自分でパスを補記する。またdllのDeclare名はmapM.dllだが、64bit Office 版のバイナリファイルは mapM64.dll なのでファイル名は変更する必要がある。
(2015/6/13に 64bit Office対応をした)

動作を確認した環境は、
Windows XP(32bit), Windows 7(64bit)
MS Office 2010以降(32bit, 64bit)

2010未満のOfficeでは、Haskell_1_Coreモジュールに2カ所ある LongPtr をLong に変更し、Declare文についている 'PtrSafe'宣言をすべて削除すれば使用可能。)

dllをコンパイル・ビルドする場合の環境はVisualStudio 2013以降のC++

*1:実質的にはC++APIの中にあって、VBA側は呼び出しているだけ

*2:小数点以下の桁数は省略して表記している

*3:iotaは連続した整数配列を出力する関数である

*4:VisualStudio2010以降でビルド確認

*5:プロジェクトエクスプローラー上で右クリック + ファイルのインポート(I)...

HaskellVBA

モジュール構成を整理し、名称を大幅に変更した。
unfoldrはいまいちパッとしないので、代わりに以下の4つの関数を追加した。

repeat_while      : 述語による条件が満たされる間繰り返し関数適用
repeat_while_not   : 述語による条件が満たされない間繰り返し関数適用
generate_while    : 述語による条件が満たされる間繰り返し関数適用の履歴を生成
generate_while_not : 述語による条件が満たされない間繰り返し関数適用の履歴を生成

 

github.com

VBA での関数型プログラミング

昨日のことだが、VBA関数型プログラミングを試みている人々がいることが分かったのでフォローしてみた。

F#がベースのようだが、F#は全然知らないので今はなんとも評価できない。

シェルスクリプトマガジン

www.amazon.co.jp

新連載「めざせ シェル女子!」