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

VBAHaskellの紹介 その15(引数の部分文字列のリストを取り出す)

VBA

関数プログラミング実践入門」に記載されている問題についての解説記事を別のところで見かけた。解説が面白かったし、VBAHaskellで実装を試みるのに手頃だったのでやってみたが、あまりいい結果にはならず課題が残った。

segments という関数で、引数に文字列を渡すと部分文字列のリストが取り出せる。例えば "ABC" に対して、["A","AB","ABC","B","BC","C"] を出力するものだ。
本に載っている実装は以下のものらしい。

segments :: [a] -> [[a]]
segments = foldr (++) [] . scanr (\a b -> [a] : map (a:) b) []

構成要素をまとめるとこうなる。

言語要素VBAHaskell今回の実装
foldr, scanr foldr, scanr*1 -
リスト結合 (++) catV 関数*2 -
リスト生成演算(:) なし 新規作成
関数合成 . 不十分 処理を分ける
ラムダ式 \ 不十分 独立した関数にする

これを実装するためにやったことは以下の通りだが、VBAHaskellの合成関数の仕組みでは今回の問題に対応するには不十分だということがわかった。

  1. Haskellconsと呼ばれているリスト生成演算を用意していなかったので、標準関数としてHaskell_4_vector.basモジュールに cons 関数を追加した。これは別に問題ではない。
  2. Haskellの文字列は文字のリストだが、VBAはそうではないため、結果表示のとき配列を文字列に変換する必要がある。foldlを使ってやればできるが、効率面も考えて StdFunモジュール に配列要素を連結する関数 joinFun を実装した(VBA組み込み関数 Join 相当)。これも問題ない。
  3. ラムダ式 \a b -> [a] : map (a:) b  の内部にあるmap (a:) b が問題になった。インラインでは書けず、以下の関数を実装することになった。
' \a b -> [a] : map (a:) b のうち、
' map (a:) b の部分
Function consMap(ByRef a As Variant, ByRef v As Variant) As Variant
    consMap = mapF(p_cons(a), v)
End Function
    Function p_consMap(

これらを準備して、以下のようなプログラムを書いた。*3

a = Array("A", "B", "C", "D", "E")
'   [a] : map (a:) b  の部分
f = p_cons(p_makeSole, p_consMap(ph_1, ph_2))         ' ポイント1
'   foldr (++) []  と  scanr f []  の実行
m = foldr(p_catV, Array(), scanr(f, Array(), a))      ' ポイント2
'   文字列として表示
printM mapF(p_join(, ""), m)
'A  AB  ABC  ABCD  ABCDE  B  BC  BCD  BCDE  C  CD  CDE  D  DE  E

「ポイント1」のところで、特化した関数consMapを組み込んでいる。「ポイント2」のところでは、foldrscanrに実引数を与えて実際に評価してしまっていて、事前にひとまとまりの関数として定義できていない。
VBAHaskellで実装している関数合成はスコープがフラットで、すべてのプレースホルダに実引数を渡して一斉に評価することしかできないのが問題で、このままでは本物のラムダ式からはほど遠い。

VBAHaskellの紹介 その14(変数のムーブ)
VBAHaskellの紹介 その13(プレースホルダの追加:_1と_2)
VBAHaskellの紹介 その1(最初はmapF)

 

*1:Haskell_0_declareモジュール に宣言

*2:Haskell_4_vectorモジュールで実装

*3:テストモジュール に サンプルsegmentsTest として追加