パスカルの3角形(VBAHaskell)

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

##1. **テーマ**
これまで何度かQiitaで紹介してきた`VBAHaskell`でちょっとしたデモみたいなことができないかと考えて、パスカルの3角形を作ってみることにしました。
高校数学の順列・組み合わせや二項定理で出てきたこれです。

![image](https://qiita-image-store.s3.amazonaws.com/0/77153/967e608e-54d1-30c0-89ff-a2ed33b11a81.png)



最上段が`1`で1段ずつ下に伸びていきます。隣どうしの数を足して下の段を作っているだけなので、どんな言語でもプログラムを書くのは簡単だと思いますが、これを`VBAHaskell`の既存の関数だけを使ってなるべく短く書くことを目標とします。標準モジュール等に何もコードを追加せず、イミディエイト ウィンドウ上だけで行います。
`VBAHaskell`のモジュール自体は下のリンクに置いてあります。

##2. **方針**
まず、「配列の隣どうしの要素の間で何かする」のにぴったりの`adjacent_op`という関数があるのでこれを使います。

```vbnet
' 1次元配列vecの隣接する要素間で2項操作 op を行う
' 出力列の要素数は 元の要素数 -1
Function adjacent_op(ByRef op As Variant, ByRef vec As Variant) As Variant
```
たとえば 配列`{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}`の隣どうしの和であれば`{3 ,5 ,7 ,9 ,11 ,13 ,15 ,17 ,19}`ができ、積であれば`{2 ,6 ,12 ,20 ,30 ,42 ,56 ,72 ,90}`ができます。

```vbnet
a = iota(1,10)     ' iota は連続する数値の配列を作る関数
printM a           ' printM はイミディエイトウィンドウに配列を出力
  1  2  3  4  5  6  7  8  9  10          ' <= 長さ10
b = adjacent_op(p_plus, a)       ' p_plus は加法 +
printM b
  3  5  7  9  11  13  15  17  19         ' <= 長さ9
c = adjacent_op(p_mult, a)       ' p_mult は乗法 *
printM c
  2  6  12  20  30  42  56  72  90       ' <= 長さ9
```
和(`p_plus`)や積(`p_mult`)はVBAHaskellで標準関数として用意されているものですが、ユーザーが独自の2項演算を定義して`adjacent_op`に渡すこともできます。
隣どうしで演算するので、配列の長さは`1`つぶん短くなります(`10`から`9`に減）。パスカルの三角形を作るには下に行くほど1つずつ長くしなければいけません。その方法としては **配列の左右に`0`を追加してから`adjacent_op`を実行する** ことにします。


![image](https://qiita-image-store.s3.amazonaws.com/0/77153/342861dc-a26f-7c52-f72a-55377ce676b4.png)



##3. **関数を合成する**
VBAHaskellは関数型っぽいプログラムを書けることを目指していて、関数の引数として関数（のようなもの）を渡したり、複数の関数を合成・ネストして関数を作ったり、関数の引数を束縛して2変数関数を1変数関数にしたりすることができます。
前のセクションで書いた方針通りに、**配列の左に`0`を追加する :point_right_tone4: 配列の右に`0`を追加する :point_right_tone4: 加法(+)で`adjacent_op`する**、という手続きを表す関数で作ってみましょう。

結論を先に書くと、それはこういう形になります。

```vbnet
f = p_adjacent_op(p_plus, p_catV(p_catV(0), 0))
```
使われている３つの関数はどれも2変数関数です。

|関数本体|p_関数|機能|
|:----------|:------------|:------------------|
|adjacent_op|p_adjacent_op|隣どうしの2項演算適用|
|plus|p_plus|加法演算(a+b)|
|catV |p_catV | 2個の1次元配列を結合|

**「関数本体」**は普通のVBA関数で**「p_関数」**はそのアドレス（AddressOfの値）を含む一種の関数オブジェクトです（実体は配列）。VBAHaskellは多くの関数とその関数オブジェクトをペアにして実装しています。例えば`p_apple`という関数が出てきたら`apple`という関数があってそちらが本体だと思ってください。
上記ではそれらが合成されており、一部の引数に`0`が置かれています。デフォルト引数として隠れているところも含めてこの関数オブジェクトを模式的に書くとこんな構造になっています。

**f = p_adjacent_op( p_plus( \___ , \___ ) , p_catV( p_catV( 0 , \_:frowning2:\_ ), 0 ) )**

\_:frowning2:\_ の個所はプレースホルダで、**後から**引数が入る場所です。そこに1次元配列`v`が入ると `catV(catV(0, v), 0)` となるので、`v`の左右に`0`を追加する動作になります。`p_plus`の引数にある`__`も同じくプレースホルダです。
**「後から」**というのがポイントで、`f = p_adjacent_op(...)`という式を実行した時点ではまだ計算本体は行われていません。`f`を実際に動かすのは`Haskell_0_declare`モジュールや`Haskell_1_Core`モジュールにある一部の関数の役割です。
ちなみに、`0`を追加するのを左→右の順ではなくて右→左の順にしたければ以下のように書きます。

```vbnet
f = p_adjacent_op(p_plus, p_catV(0, p_catV(, 0)))
```
では、この合成された関数を実際に動かしてみましょう。初期値は`Array(1)`です。

##4. **繰り返し処理**
パスカルの三角形は無限に続いていくので、実際のプログラムは「第N段まで」という形で表すことにします。
単純に繰り返し処理を行う関数として、現状VBAHaskellに４つの関数を用意しています。

|関数|機能|
|:---|:---|
|repeat_while|条件が満たされる間繰り返し関数適用|
|repeat_while_not|条件が満たされない間繰り返し関数適用|
|generate_while|条件が満たされる間繰り返し関数適用の履歴を生成|
|generate_while_not|条件が満たされない間繰り返し関数適用の履歴を生成|

三角形の第N段目だけでなく、作った1段目～N段目の全体を履歴として表示したいので、3番目の`generate_while`を使うことにします。

```vbnet
' 述語による条件が満たされる間繰り返し関数適用の履歴を生成
Function generate_while(ByVal val As Variant , _         ' ← 初期値
                        ByRef pred As Variant, _         ' ← 条件
                        ByRef fun As Variant , _         ' ← 関数
                        Optional ByVal n As Long = -1 _  ' ← 回数上限
       ) As Variant
```
2番目の引数は繰り返しを続ける条件ですが、特に条件は付けず回数指定=8で動かすとこうなります。

```vbnet
' 初期値配列 a
a = Array(1)
' 繰り返し適用する関数を定義
f = p_adjacent_op(p_plus, p_catV(p_catV(0), 0))
' f を繰り返し適用してその履歴を生成
result = generate_while(a, p_true, f, 8)    ' p_true は恒真条件
' サイズを表示
printS  result
[Dim1]: 0 -> 8  : Total Size = 9
' 個々の要素を表示
printM  result    ' ↓　ジャグ配列（配列の配列）なのでこうなる
  [0]  [1]  [2]  [3]  [4]  [5]  [6]  [7]  [8]
' printM_ でジャグ配列の要素を順に表示
printM_  result
  1
  1  1
  1  2  1
  1  3  3  1
  1  4  6  4  1
  1  5  10  10  5  1
  1  6  15  20  15  6  1
  1  7  21  35  35  21  7  1
  1  8  28  56  70  56  28  8  1
```
少し長いですが1行のコードにすることもできます。

```vbnet
printM_  generate_while(Array(1), p_true, p_adjacent_op(p_plus, p_catV(p_catV(0), 0)), 8)
  1
  1  1
  1  2  1
  1  3  3  1
  1  4  6  4  1
  1  5  10  10  5  1
  1  6  15  20  15  6  1
  1  7  21  35  35  21  7  1
```

##5. **他の書き方(1)**
大して変わりばえしませんが、`generate_while`に回数上限ではなく終了条件を指定してみます。第2引数に「長さがN未満だったら続ける」という条件を与えるバージョンがこちらです。

```vbnet
' a と f は前のと同じ
printM_  generate_while(a, p_less(p_sizeof, 8), f)
  1
  1  1
  1  2  1
  1  3  3  1
  1  4  6  4  1
  1  5  10  10  5  1
  1  6  15  20  15  6  1
  1  7  21  35  35  21  7  1
```
`less`は「小なり比較`(a < b)`」関数、`sizeof`は配列の長さを取得する関数です。
`p_sizeof`にデフォルト引数が隠れていて **p_less( p_sizeof( \_:frowning2:\_ ) , 8 ) )** という構造になっているので、プレースホルダ（\_:frowning2:\_）のところに `v` が代入されると、`less(sizeof(v), 0)` すなわち「配列`v`の長さ < 8」の判定結果が返ってきます。
これが満たされるのは7段目の`{1 ,6 ,15 ,20 ,15 ,6 ,1}`のところまでで、満たされなくなった8段目のところで止まる仕組みです。条件が満たされる限り無限ループになるので少し危険です。

##6. **他の書き方(2)**
ただのループではなく、もうちょっとHaskellっぽい`fold/scan`系の関数を使ってみます。

|関数|機能|
|:--|:--|
|foldl|左畳み込み（初期値指定あり）|
|foldr|右畳み込み（初期値指定あり）|
|foldl1|左畳み込み（初期値=先頭要素）|
|foldr1|右畳み込み（初期値=末尾要素）|
|scanl|左scan（初期値指定あり）|
|scanr|右scan（初期値指定あり）|
|scanl1|左scan（初期値=先頭要素）|
|scanr1|右scan（初期値=末尾要素）|
fold（畳み込み）というのは2項関数を列の全要素にわたって順に実行していくやり方のひとつで、列の左側から処理していく`foldl`と右側から処理していく`foldr`に分かれます。

関数`f`と初期値`#`, 列`{a, b, c, ... , x, y, z}`があるとして、`foldl`は`a, b, c, ...` の順に関数に食べさせていきます。
　`f(...(f(f(f(#,a),b),c),...),x),y),z)`
`foldr`は逆に`z, y, x`の順に食べていきます。
　`f(a,f(b,f(c, ..., f(x,f(y,f(z,#)))...)))`

```vbnet
' foldl で 0 から {1, 2, ..., 10} を足していった合計値を出す
printM foldl(p_plus, 0, iota(1, 10))
 55 
' scanl だと過程も得られる
printM scanl(p_plus, 0, iota(1, 10))
  0  1  3  6  10  15  21  28  36  45  55
```
`scan`系は結果だけでなく、畳みこみの過程をすべて保持して返すもので、今回はこれを使います。

ここで発想を逆転させて、データではなくて関数`f`の方が`{f, f, f, ... , f}`と並んでいるとしましょう。
この列を上手く使えば、
((((初期値と`f`を作用させた結果)
　　　　 と`f`を作用させた結果 ))
　　　　　 と`f`を作用させた結果)
　　　　　　　　・・・
という形で繰り返しを表現できます。
値`x`と関数`f`を**作用させる**とはつまり`x`と`f`から`f(x)`を導くことであり、それを行う関数`applyFun`が用意されています。

```vbnet
' 関数適用関数  1引数に対して関数を適用する
Function applyFun(ByRef param As Variant, _
                  ByRef func  As Variant) _
         As Variant
```
これを`scanl`に渡して左から順に`{f, f, f,...}`を渡していけば出来上がりです。

```vbnet
' repeat は同一データを任意の個数並べた配列を作る関数
' a と f は前のと同じ
printM_  scanl(p_applyFun, a, repeat(f, 8))
  1
  1  1
  1  2  1
  1  3  3  1
  1  4  6  4  1
  1  5  10  10  5  1
  1  6  15  20  15  6  1
  1  7  21  35  35  21  7  1
  1  8  28  56  70  56  28  8  1
```

##7. **まとめ**
イミディエイト ウィンドウ上でパスカルの三角形を表示するコードは88文字になりました。

`printM_ generate_while(Array(1), p_true, p_adjacent_op(p_plus, p_catV(p_catV(0), 0)), 8)`

関数合成と繰り返し処理の抽象化によって汎用部品だけで構成できました。しかし`p_true`とか`p_plus`といった関数オブジェクトをライブラリ側にいちいち用意しておかなければならない点が不便です。ベース言語がVBAである以上どうしようもないのか、C++APIを含めたライブラリ側で何か工夫の余地はないか、もう少し考えないとわかりません。ただしコードの短さという点ではこのへんが限界なのではないかと思っています。


* * *
**リンク**

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