「関数プログラミング実践入門」に記載されている問題についての解説記事を別のところで見かけた。解説が面白かったし、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の合成関数の仕組みでは今回の問題に対応するには不十分だということがわかった。
- Haskellで
cons
と呼ばれているリスト生成演算を用意していなかったので、標準関数としてHaskell_4_vector.basモジュールにcons
関数を追加した。これは別に問題ではない。 - Haskellの文字列は文字のリストだが、VBAはそうではないため、結果表示のとき配列を文字列に変換する必要がある。
foldl
を使ってやればできるが、効率面も考えて StdFunモジュール に配列要素を連結する関数joinFun
を実装した(VBA組み込み関数Join
相当)。これも問題ない。 - ラムダ式
\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」のところでは、foldr
とscanr
に実引数を与えて実際に評価してしまっていて、事前にひとまとまりの関数として定義できていない。
VBAHaskellで実装している関数合成はスコープがフラットで、すべてのプレースホルダに実引数を渡して一斉に評価することしかできないのが問題で、このままでは本物のラムダ式からはほど遠い。
VBAHaskellの紹介 その14(変数のムーブ)
VBAHaskellの紹介 その13(プレースホルダの追加:_1と_2)
VBAHaskellの紹介 その1(最初はmapF)