##はじめに
これは、Visual Basic Advent Calendar 2016の16日目の記事です。
これまで2回Advent Calendar 2016に紹介記事を書かせて頂きましたが、2回ともデモっぽい内容だったので、今回はちゃんとVBAHaskellにおける関数の作り方について書きたいと思います。
##関数にできるもの
VBAHaskellでは、関数本体とそれとペアになる関数オブジェクトを定義して、ゆるふわな関数型っぽいプログラムを書くことができます。しかしその使い方ができる関数のパターンは一つだけです。
' VBAHaskellで基本的に定義できる たったひとつの関数パターン
Function f(ByRef a As Variant, ByRef b As Variant) As Variant
' ただし、第二引数は Optional にすることはできる
Function g(ByRef a As Variant, Optional ByRef b As Variant) As Variant
これだけですが、以下の3つの方法で用途を広げられます。
##配列関数の引数にする
plus
と、そのペアであるp_plus
は単に足し算するだけの関数ですが、mapF
やzipWith
、fold/scan
系の関数の引数にすれば大量のデータを処理できます。
' ふたつの配列の各要素を加える
printM zipWith(p_plus, iota(0, 9), iota(30000, 30009))
30000 30002 30004 30006 30008 30010 30012 30014 30016 30018
zipWith
は二つの配列から新たに配列を作成する関数ですが、p_plus
がその第1引数になっています。API内でループして0+30000, 1+30001, ..., 9+30009
という配列を作ってます。引き算にしたければp_minus
を、掛け算にしたければp_mult
を指定します。
' 自然数を順次足していく
printM scanl1(p_plus, iota(0, 100))
0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 210 231 253 276 300 325 351 378 406 435 465 496 528 561 595 630 666 703 741 780 820 861 903 946 990 1035 1081 1128 1176 1225 1275 1326 1378 1431 1485 1540 1596 1653 1711 1770 1830 1891 1953 2016 2080 2145 2211 2278 2346 2415 2485 2556 2628 2701 2775 2850 2926 3003 3081 3160 3240 3321 3403 3486 3570 3655 3741 3828 3916 4005 4095 4186 4278 4371 4465 4560 4656 4753 4851 4950 5050
同じくp_plus
を使ってますが、端からどんどん処理して履歴を出すscanl1
に使うと上のようになります。
##関数を合成する
less
とp_less
は大小関係 "<"
表しますが、文字列の長さを求めるp_len
と組み合わせることによって、配列を「文字列の長さ順」にソートすることができます。
' String の配列
s = Array("Zbkn", "Afkbpirthrit", "123", "Kdgndi")
' 文字列の長さ順にしたときの順位 ord を出力する
ord = sortIndex_pred(s, p_less(p_len, p_len))
printM ord
2 0 3 1
' Ord 順に並べる
printM subV(s, ord)
123 Zbkn Kdgndi Afkbpirthrit
上の例では、p_less(p_len, p_len)
がソートに使う比較関数となり、デフォルト引数が隠れているので実際にはこんな構造です。
p_less(p_len( _ _ ), p_len( _ _ ))
これにふたつの文字列s1
とs2
を代入すると Len(s1) < Len(s2)
となって、文字列長の比較になります。個々の関数は単機能でも合成すれば複雑なものを作れるので、目的ごとにいちいち Function
プロシージャを書いて関数を定義せずにすみます。たとえば文字列長で降順ソートしたければp_greater
を使えばいいです。
' String の配列
s = Array("Zbkn", "Afkbpirthrit", "123", "Kdgndi")
' 文字列の長さ順(降順)にしたときの順位 ord を出力する
ord = sortIndex_pred(s, p_greater(p_len, p_len))
printM ord
1 3 0 2
' Ord 順に並べる
printM subV(s, ord)
Afkbpirthrit Kdgndi Zbkn 123
##引数、戻り値を配列にする
自分自身が配列を引数にとることで、多数の要素を相手にした処理をすることができます。戻り値を配列にするのも同様です。
たとえばfilterR
という関数は、第1引数に対象配列、第2引数に<0/1>フラグからなる配列を指定することによって、配列をフィルタリングをします。
'ベクトル・配列の(行の)フィルタリング
'Flgは 0/1
Public Function filterR(ByRef data As Variant, ByRef flg As Variant) As Variant
下は整数列から3の倍数だけ抽出する単純な例ですが、フラグを作るのに関数合成を使っています。
a = iota(0, 30) ' 0から30までの整数列
f = p_equal(p_mod(, 3), 0) ' (x Mod 3) = 0 を表す関数
printM filterR(a, mapF(f, a))
0 3 6 9 12 15 18 21 24 27 30
##関数オブジェクトの作り方
Optional引数のないいちばん身近な例は足し算です。
この関数は、Haskell_2_stdFunモジュール(Github、もしくはここ)にあります。
'加算
Function plus(ByRef a As Variant, ByRef b As Variant) As Variant
plus = a + b
End Function
Function p_plus(Optional ByRef firstParam As Variant, Optional ByRef secondParam As Variant) As Variant
p_plus = make_funPointer(AddressOf plus, firstParam, secondParam)
End Function
Function plus
は最初に述べたパターンを守って普通に定義したVBA関数です。
p_plus
の方はplus
のペアであることを明確にするためにp_plus
という名前にしていますが、AddressOf
の引数がplus
になっていることが本質です。
自分で新しく関数とその関数オブジェクトを作成するときは、これをコピペして関数名だけ変えればOKです。make_funPointer
がすべての仕事をやっています。
第二引数がOptionalな場合は、それが全くのダミーであるかそうでないかでパターンが分かれます。
次の指数関数 expN
は組み込みのexp
そのまんまですが、第二引数は名前の通りダミーで、あってもなくても無影響です。IsMissing
の判定もしていません。この場合は、最初のパターンと同じで、make_funPointer
で関数オブジェクトを作ります。
'指数関数
Function expN(ByRef a As Variant, ByRef dummy As Variant) As Variant
expN = Exp(a)
End Function
Function p_exp(Optional ByRef firstParam As Variant, Optional ByRef secondParam As Variant) As Variant
p_exp = make_funPointer(AddressOf expN, firstParam, secondParam)
End Function
対数関数の場合は第二引数がなければ自然対数、あればその数を底にするため、IsMissing
で判定をしています。こういう関数に対してはmake_funPointer
の代わりにmake_funPointer_with_2nd_Default
を使います。
'対数関数
Function logN(ByRef a As Variant, Optional ByRef base As Variant) As Variant
If IsMissing(base) Then
logN = Log(a)
Else
logN = Log(a) / Log(base)
End If
End Function
Function p_log(Optional ByRef firstParam As Variant, Optional ByRef secondParam As Variant) As Variant
p_log = make_funPointer_with_2nd_Default(AddressOf logN, firstParam, secondParam)
End Function
make_funPointer
とmake_funPointer_with_2nd_Default
がやっていることはAPIとの絡みもあるので複雑ですが、どちらを使うかの基準は上に述べた通り単純なので、p_plus
かp_log
のどちらかをコピペするという方針で十分です。
リンク
Qiita VBAHaskellの紹介 その1 (最初はmapF)
ソースコード(Github)
VBAHaskellの関数リファレンス
dllバイナリ:
https://github.com/mYmd/VBA/blob/master/bin/mapM (32bit-Office用)
https://github.com/mYmd/VBA/blob/master/bin/mapM64 (64bit-Officey用)
VBAコード添付済みExcelブック
VBAHaskellほぼ全部入り.xlsm