配列のクリップボード転送(VBAHaskell)

これは、[Visual Basic Advent Calendar 2016](http://qiita.com/advent-calendar/2016/vb)の12日目の記事です。

##1. **テーマ**
2次元以下の配列のデータをクリップボードに転送します。
行内は`Tab`で、行間は`CrLf`で繋げた文字列形式で出力します。文字列にできない要素（NullやEmpty、配列など）は長さゼロの文字列`""`とします。

```vbnet
a = makeM(3, 4, iota(1,12))                  ' 配列の作成
a(0,1) = Null : a(1,2)= "az" : a(2,1)= "愛"  ' Nullや文字を入れる
m2Clip  a                                    ' クリップボード転送
```
「`m2Clip`」でクリップボードに転送し、エディタ等にペーストできます。
![image](https://qiita-image-store.s3.amazonaws.com/0/77153/9436f3f8-e1e8-ce8e-07be-2aef44bb96c4.png)

転送対象の文字列を作る部分が当記事のメインです。

##2. **コード**
最初にコードを示します。モジュールは、[Y_IO_utiliy.bas(Github)](https://github.com/mYmd/other/blob/master/Y_IO_utiliy.bas)または[Y_IO_utiliy.bas](http://home.b07.itscom.net/m-yamada/misc/Y_IO_utiliy.bas)に置いています。

```vbnet
' 配列（2次元以下）をクリップボードに転送する
Public Sub m2Clip(ByRef data As Variant)
    Dim s As String
    ' -------------------------
    ' ↓ 前半部分。クリップボードに貼り付けるための文字列を作る
    Select Case Dimension(data)
    Case 0
        s = CStr_(data) & vbCrLf
    Case 1
        s = Join(mapF(p_CStr, data), vbTab) & vbCrLf
    Case 2
        Dim tmp As Variant
        tmp = zipC(mapF(p_CStr, data))
        tmp = mapF(p_join(, vbTab), tmp)
        s = Join(tmp, vbCrLf) & vbCrLf
    End Select
    ' ↑ 前半部分ここまで。
    ' -------------------------
    ' ↓ 後半部分。文字列をクリップボードに貼り付ける
    Dim dOb As Object
    'Set dOb = New MSFORMS.DataObject
    Set dOb = CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
    With dOb
        .SetText s
        .PutInClipboard
    End With
    Set dOb = Nothing
End Sub
```
前半部分で、配列の次元 (`0, 1, 2`) によって場合分けしています。
`Case 0`は実際には配列ではなくてスカラーです。関数`CStr_`はVBA組み込みの`CStr`をちょっと変えたもので、文字列にできるデータは文字列化し、できないデータは長さゼロの文字列`""`にするだけの関数です。

`Case 1`は1次元配列、`Case 2`は2次元配列で、ここに出てくる`mapF`はVBAHaskellの関数で最も頻繁に使われるものです。

##3. **配列要素のString変換**
`data`が1次元配列の場合、本質的には **`Join(data, vbTab) & vbCrLf`** とするだけですが、要素の型によっては`Join`がエラーになる[^1]ので事前に`String`に変換する必要があります。
　`mapF(p_CStr, data)　　　'p_CStr は CStr_ の関数オブジェクト`
このマッピングによって、`data =` { x<sub>0</sub>, x<sub>1</sub>, x<sub>2</sub>, ... ,  x<sub>n</sub> } として 
{x<sub>0</sub>, x<sub>1</sub>, x<sub>2</sub>, ... ,  x<sub>n</sub>}　 :point_right_tone4: 　`{CStr_`(x<sub>0</sub>) , `CStr_`(x<sub>1</sub>) , `CStr_`(x<sub>2</sub>) , ... , `CStr_`(x<sub>n</sub>)}
と変換されます。
[7日目の記事](http://qiita.com/mmYYmmdd/items/e38a0fd00326d918e41b) でも書きましたが、関数本体である`CStr_`は普通のVBA関数で、`p_CStr`はそのアドレス（AddressOfの値）を含む一種の関数オブジェクト(p_関数)です（実体は配列）。

`data`が2次元配列`x(i, j)`のときの手順は以下の通りです。
　(1) 各要素を文字列化する
　　　`tmp = mapF(p_CStr, data)`
　(2) 行単位で配列化する
　　　`tmp = zipC(tmp)`
　(3) 各行に対して`Join( , vbTab)`する
　　　`tmp = mapF(p_join(, vbTab), tmp)`
　(4) `Join( , vbCrLf)`し、`vbCrLf`を追加する
　　　`s = Join(tmp, vbCrLf) & vbCrLf`

(1) は前に説明した通りです。
(2) `Join`は配列自体を引数に取るので、行を一つの単位として配列化する必要があります。`zipC`はジャグ配列を作る関数の一つで、下にその概念を図で示しています。
(3) も前に説明した通りですが、`mapF`を使ってループを書かずに済んでいます。
(4) 行間は`Tab`ではなく`CrLf`で繋いでいます。

```vbnet
'ふたつの配列の対応する要素どうしをmakePairしてジャグ配列を作る
Public Function zip(ByRef a As Variant, ByRef b As Variant) As Variant

'２次元配列の各行ベクトルをzipVs
Public Function zipR(ByRef m As Variant, Optional ByRef target As Variant) As Variant

'２次元配列の各列ベクトルをzipVs
Public Function zipC(ByRef m As Variant, Optional ByRef target As Variant) As Variant
```

![image](https://qiita-image-store.s3.amazonaws.com/0/77153/43daafc7-79b3-3eab-28c7-3ae6046c5d39.png)

実際のコードでは(1)と(2)を合わせて`tmp = zipC(mapF(p_CStr, data))`と書いています。

##4. **クリップボード転送**
Officeアプリによっては参照設定に`MSFORMS.DataObject`が選べないようなので、`New MSFORMS.DataObject`ではなく`CreateObject`を使っています。

`DataObject::SetText`メソッドはUnicodeで書き込まれるせいか、カナ漢字等が含まれている場合はVBAのモジュールやイミディエイト ウィンドウにはペーストできません。最初クリップボード転送自体が失敗しているのかと思いましたが、外部エディタやワークシートにはペーストできるのでよしとしています[^2]。 イミディエイト ウィンドウに配列を出力するのは`printM`関数が使えます。


* * *
**リンク**

[VBAHaskellのI/Oユーティリティ](http://qiita.com/mmYYmmdd/items/962b0e2799d356b87aed)
[Qiita　VBAHaskellの紹介　その1　（最初はmapF）](http://qiita.com/mmYYmmdd/items/c731edf943acc0a0ebe9)
[ソースコード(Github)](https://github.com/mYmd/VBA)
[VBAHaskellの関数リファレンス](http://home.b07.itscom.net/m-yamada/vh_Manual/VBAHaskell_reference.htm)
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](https://github.com/mYmd/VBA/blob/master/bin/VBAHaskell%E3%81%BB%E3%81%BC%E5%85%A8%E9%83%A8%E5%85%A5%E3%82%8A.xlsm)



[^1]: Nullや配列などが含まれているとエラーになります
[^2]: Win32APIを使った方法もあるみたいですが、やる気がないです