VBAHaskellで2変数関数を合成するときの自由度を高めるために新しいプレースホルダ ph_1 と ph_2 を導入した。これの直接のきっかけは以下の問題 1 を解くことだった。
問題4
正の整数のリストを与えられたとき、数を並び替えて可能な最大数を返す関数を記述せよ。例えば、[50, 2, 1, 9]が与えられた時、95021が答えとなる。
これには下のような比較関数を使ってソートする必要がある。2
'比較関数(aとbは数字だけからなる文字列とする)
Function Question4Comp(ByRef a As Variant, ByRef b As Variant) As Variant
'a,bの順に結合した文字列を数値化 < b,aの順に結合した文字列を数値化
Question4Comp = IIf(Val(a & b) < Val(b & a), 1, 0)
End Function
もちろんこれでいいのだが、こういう単純な比較関数はいちいちモジュールに書くのではなく、関数合成で作り出したい。しかし今までの実装では難しかったので、C++側を改造しVBA側で新しく2種類のプレースホルダを追加した。
これまでの実装では比較関数 f(x, y) の中で何か計算をしようとして関数を合成しても、f(g(x), h(y)) という形にしかならなかった 3。 f(x, y) の x には第1引数のみ、y には第2引数のみを渡していたのである。実引数(a, b)を代入すると f(g(a), h(b)) となるので、a と b は分離され結合文字列を作ることができない。ab を逆順の ba にすることも難しい。
単純に f(g(a, b), h(a, b)) を評価するように変えればいいが、合成されていないただの f(x, y) に適用したときには f(a, b) となるべきで、 f(a, a) とか f(b, b) になってはいけない。もちろんVBAのユーザーコードは修正不要にしたい。
そこで、C++標準ライブラリのstd::placeholdersにある _1 や _2 のような明示的なプレースホルダを導入することにした。これまでプレースホルダは関数の中で置かれている位置によって第1引数を受け取るか第2引数を受け取るかが決まっていたが、これらは場所に依存せずに受け取る引数を決め打ちできる。もちろん _1 は第1引数を受け取り、_2 は第2引数を受け取るプレースホルダだ。
VBAではアンダースコア '_' で始まる変数や関数がエラーになってしまうので、_1、_2 ではなく ph_1 と ph_2 と名付けている 4。
具体的にはこうなる。
' f(x, y) = CLng(x & y) < CLng(y & x) と同じ
comp4 = p_less(p_getCLng(p_plus(ph_1, ph_2)), p_getCLng(p_plus(ph_2, ph_1)))
' dumpFunで中身を見てみる
?dumpFun(comp4)
F8652(F1868(F6828(_1, _2), _), F1868(F6828(_2, _1), _))
これを使って対象の配列に対するソートインデックスを作ればいい。ただし問題が「最大数」を求めるものなので逆順にしてから適用する。
' 上の方のcomp4を使う
s = sortIndex_pred(arr, comp4) ' ソートインデックスを出力
result = subM(arr, reverse(s)) ' 逆順に並べ替える
このサンプルをテストモジュールに追加した。(Sub sortTest2)
VBAHaskellの紹介 その12(1時間以内に解けなければプログラマ失格がなんたら)
VBAHaskellの紹介 その1(最初はmapF)
-
これの説明は、ブログ 比較関数の自然な例(2) の方に書いた。 ↩
-
正確には g と h は2変数関数のうち1変数を束縛したものなので、f(g(x, x), h(y, y)) が正しい。 ↩
-
Haskell_1_Core.basモジュール に追加。 ↩