LoginSignup
0
1

More than 5 years have passed since last update.

VBAHaskellの紹介 その19(ParamArrayとswapVariant)

Last updated at Posted at 2015-07-02

VBAの ParamArray の仕組みがどうもよくわからない。

MSDNには「プロシージャが、省略可能なパラメーターとして、指定されたデータ型の要素の配列を受け取ることを示します。 ParamArray は、パラメーター リストの最後のパラメーターにのみ使用できます。」とある。
要するに可変長引数であり、受け取ったプロシージャ側からその引数はひとつの配列のように見える。配列なので LBoundUBound の値も取れるし、For ループで個々の要素にアクセスすることもできる。
しかしこれは 他のプロシージャに ByRef で渡すことができない、という制約があって、下のコードだと bar1 に渡そうとするとエラーになる(「コンパイルエラー:ParamArrayの使い方が適切ではありません。」というダイアログメッセージが出る)。
ByValbar2 に渡すことは普通にできる。しかし bar1 に渡すにはローカルでコピーをとって渡すしかないようだ。

Function foo(ParamArray v() As Variant)
    bar1 v     ' これはNG
    bar2 v     ' これはOK
    Dim tmp As Variant : tmp = v
    bar1 tmp   ' ← コピーならOK
    ...
Sub bar1(ByRef x As Variant)     ' ByRef渡し
Sub bar2(ByVal x As Variant)     ' ByVal渡し

VBAHaskellで ParamArray を使っている箇所は少ないが、そのひとつに catVs 1 がある。任意個の1次元配列をつなげて配列を返す関数で、以前は下のような実装になっていた。ふたつの配列をつなげる catV 関数を foldl1 する、という素朴なやり方だ。

'ベクトルを結合(可変長引数)
Function catVs(ParamArray vectors() As Variant) As Variant
    catVs = foldl1(p_catV, VBA.Array(vectors)(0))
End Function

VBA.Array(vectors)(0) の部分が分かりにくいと思うが、少し書き直すと結局こういうことをやっているのと同じで、引数vectorsのコピーを作っている。

Function catVs(ParamArray vectors() As Variant) As Variant
    Dim tmp As Variant : tmp = vectors    ' コピー
    catVs = foldl1(p_catV, tmp)
End Function

これはfoldl1 関数に引数 vectors をそのまま渡すことができないせいだが、もちろん効率は悪く、個々の配列が大きいときは無視できないコストになる。なにより無意味なコピーは無くしたいという気持ちが強い。
これには swapVariant 2 を使って変数をスワップするのが有効な手段で、最近以下のように書き換えた。

'ベクトルを結合(可変長引数)
Function catVs(ParamArray vectors() As Variant) As Variant
    Dim i As Long
    Dim tmp As Variant
    If LBound(vectors) <= UBound(vectors) Then
        ReDim tmp(LBound(vectors) To UBound(vectors))
        For i = LBound(vectors) To UBound(vectors)
            swapVariant vectors(i), tmp(i)    ' スワップする
        Next i
        catVs = foldl1(p_catV, tmp)      ' 実際の処理
        For i = LBound(vectors) To UBound(vectors)
            swapVariant vectors(i), tmp(i)    ' 元に戻す
        Next i
    End If
End Function

もとの引数である vectors と同じ長さの配列 tmpを作り、個々の要素(それ自体が配列)をスワップして入れている。要素が巨大な配列だとしてもそのコストは無視できる程度だ。こうして作った tmp は通常の配列と同様に扱うことができるので、foldl1 で処理すれば目的が達成できる。最後にもう一度スワップして元に戻してやる。
VBAHaskellで目標にしているループの排除が犠牲になっているが、これは課題としたい。


VBAHaskellの紹介 その18(reverseをfoldで実装)
VBAHaskellの紹介 その1(最初はmapF)
ソース https://github.com/mYmd/VBA

0
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1