VBAHaskellの紹介 その4 (Find)
きょう、いわゆる検索関数 "find_pred" を追加した。Haskell_1_Coreモジュールに入れている。
Function find_pred(ByRef pred As Variant, ByRef vec As Variant) As Variant
対象配列の中から条件に合致する値を探す関数で、第1引数のpredは検索における条件指定の述語で、第2引数のvecは対象配列だ。
自由度の高いpredを簡単に生成できるかどうかが検索の使い勝手を決めると思う。「指定した範囲に含まれる」とか「素数である」等の条件を述語オブジェクトとして生成するときに、いちいちヘルパ関数を定義したりするのは面倒なのでやりたくない。*1
find_predは単にループを回しているだけだが、合成関数の作成と呼び出しが簡単なので、シンプルに実装できた。
これを使って乱数列の中からある範囲内の数値を探すコードはこうなる*2 。
Points = mapF(p_rnd(0), repeat(100, 10000))
pred = p_mult(p_greater( , 29.9), p_less( , 29.99))
m = find_pred(z, Points)
こう書くと pred は 29.9 < x かつ x < 29.99 を満たす x に1を、そうでない x に0を返す関数になる。ラムダ式のような自由と柔軟さはないが、C++03でbind1stやbind2ndを組み合わせて生成するよりはマシかもしれない。
find_pred の全体は以下の通り。
Function find_pred(ByRef pred As Variant, ByRef vec As Variant) As Variant
Dim z As Variant
Dim i As Long: i = LBound(vec)
For Each z In vec
If applyFun(z, pred) Then
find_pred = i
Exit Function
End If
i = i + 1
Next z
find_pred = Null
End FunctionFunction find_pred(ByRef pred As Variant, ByRef vec As Variant) As Variant
If Dimension(vec) = 1 Then
find_pred = find_imple(pred, vec, UBound(vec) + 1)
If find_pred = UBound(vec) + 1 Then find_pred = Null
Else
find_pred = Null
End If
End Function
個々の配列要素に対してapplyFun(z, pred) で pred を呼び出すだけである。
(2015/4/14)
効率上の理由からVBA側でループするのではなくAPIに find_imple 関数を追加してそれを呼び出す形に変更した。C++側でイテレーションのたびにpred の構築と破棄を繰り返すのは無駄だから。
ところで引数の順序が find_pred(pred, vec) とするか find_pred(vec, pred) とするか決め難かった。とりあえず述語を前にしたが、ソート関連の関数*3では比較関数が後になっている。
VBAHaskellの紹介 その3 (FizzBuzz)
http://mmyymmdd.hatenablog.com/entry/2015/04/11/130016
VBAHaskellの紹介 その2 (合成関数)
http://mmyymmdd.hatenablog.com/entry/2015/04/11/112139
VBAHaskellの紹介 その1 (最初はmapF)
http://mmyymmdd.hatenablog.com/entry/2015/04/11/095044
VBAHaskellの紹介 その5 (関数のシグネチャ)
VBAHaskellはVBAの関数を合成して独立したオブジェクトにしたり、Haskell的なリスト処理をするライブラリだが、それが可能なVBA関数のシグネチャは実質的に1種類しかない。
Function myFunction(ByRef a As Variant, ByRef b As Variant) As Variant
または
Function myFunction(ByRef a As Variant, Optional ByRef b As Variant) As Variant
この関数をそのまま使うのではなく、一種の関数ポインタ的なものにして渡す必要があって、それをやるヘルパ関数が
make_funPointer と make_funPointer_with_2nd_Default だ。
たとえば Haskell_2_stdFunモジュールで最初に定義されている firstArg 関数はこうなっている。
Function firstArg(ByRef a As Variant, ByRef b As Variant) As Variant
firstArg = a
End Function
Function p_firstArg(Optional ByRef firstParam As Variant, _Optional ByRef secondParam As Variant) As Variant
p_firstArg = make_funPointer(AddressOf firstArg, firstParam, secondParam)
End Function
firstArg 関数に付属する p_firstArg が関数ポインタを作っており、その中でmake_funPointerを呼んでいる。関数名は何でもよいが、p_関数名 にだいたい統一している。新たに関数を定義したら、この p_firstArg のコードをコピペして赤文字のところだけ対象関数に合わせて修正すれば使えるようになる。*1
make_funPointer は4要素の配列を作っているだけで、その配列は(関数ポインタ、引数1、引数2、xxx)という構成になっている。2つある引数には他の関数をネストすることもできて、これによって関数合成が実現される。
C++API側ではこのネストした配列を受け取って頻繁に呼び出すことになるが、VBAのsafearray構造体の中身に毎回アクセスするのは効率が悪いと思われるので、再帰的なリンク構造を再構成してから呼び出すようにしている。具体的には VBA_NestFunc.hpp と VBA_NestFunc.cpp にある funcExpr_i::eval() の再帰的な呼び出しがそれにあたる。
残念ながらここにはコンパイル時処理はない。
VBAHaskellの紹介 その4 (Find)
http://mmyymmdd.hatenablog.com/entry/2015/04/12/113746
VBAHaskellの紹介 その3 (FizzBuzz)
http://mmyymmdd.hatenablog.com/entry/2015/04/11/130016
VBAHaskellの紹介 その2 (合成関数)
http://mmyymmdd.hatenablog.com/entry/2015/04/11/112139
VBAHaskellの紹介 その1 (最初はmapF)
http://mmyymmdd.hatenablog.com/entry/2015/04/11/095044
ソースコード:
dllのバイナリ: