読者です 読者をやめる 読者になる 読者になる

VBAHaskellの紹介 その13 (プレースホルダの追加: _1 と _2 )

VBAHaskellで2変数関数を合成するときの自由度を高めるために新しいプレースホルダ ph_1 と ph_2 を導入した。これの直接のきっかけは以下の問題 *1 を解くことだった。

問題4
正の整数のリストを与えられたとき、数を並び替えて可能な最大数を返す関数を記述せよ。例えば、[50, 2, 1, 9]が与えられた時、95021が答えとなる。

これには下のような比較関数を使ってソートする必要がある。(())

'比較関数(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)) という形にしかならなかった *2 が正しい。))。 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 と名付けている *3

具体的にはこうなる。

' 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)

*1:1時間以内に解けなければプログラマ失格となってしまう5つの問題が話題に の問題4

*2:正確には g と h は2変数関数のうち1変数を束縛したものなので、f(g(x, x), h(y, y

*3:Haskell_1_Core.basモジュール に追加。