Vim script で flatmap
しょーもない話ですが最近になって Vim script を書き始めたので。
全く知らなかったのですが、 Vim 7.0 あたりから map
関数や filter
関数が実装されていた模様。
また、 Vim 8.0 からはラムダ式が、 8.2 からは UFCS っぽいもの1が導入されたそうです。
map
や filter
があるなら flatmap
もあっていいだろうと。
※ なお、 map
filter
は新しくリストや辞書を作るのではなく、与えられたオブジェクトそのものを変化させるので、他の言語のノリで使うとハマります。 copy
を予め使うといい感じです。2
さて、実装はこんな感じです。
function! FlatMap(obj, body) abort let l:privates = {} function l:privates.for_list() abort closure let l:new_list = [] for l:x in a:obj let l:new_list += a:body(l:x) endfor return l:new_list endfunction function l:privates.for_dict() abort closure let l:new_dict = {} for l:item in values(a:obj) for [l:key, l:val] in items(l:item) let l:new_dict[l:key] = a:body(l:val) endfor endfor return l:new_dict endfunction return get({ \ v:t_list: l:privates.for_list, \ v:t_dict: l:privates.for_dict, \ }, type(a:obj), { -> v:null })() endfunction
これで…
" [1, 1, 2, 2, 3, 3] echo [1, 2, 3]->FlatMap({ x -> [x, x] }) " [1, 2, 3, 2, 4, 6, 3, 6, 9] echo [ \ { 'inner': [1, 2, 3] }, \ { 'inner': [2, 4, 6] }, \ { 'inner': [3, 6, 9] }, \ ]->FlatMap({ x -> x.inner }) " {'1': '(10)', '2', '(20)', 'bbb': '(BBB)', 'aaa': '(AAA)'} echo { 'a': { 'aaa': 'AAA', 'bbb': 'BBB' }, '1': { '1': 10, '2': 20 } } \ ->FlatMap({ x -> printf('(%s)', x) }) " v:null echo (100)->FlatMap({ x -> [x, x] })
てな感じで、ほぼほぼ想定通りに動きました。
map
や filter
が辞書を引数に取り得るので、今回はそれに倣い辞書の場合も想定してみました。
ただし、特にメリットが分からないので map
filter
とは違い新たにオブジェクトを作り直しています。
ローカルスコープの関数を定義することができないので、ローカルレベルで辞書を作成して中身に関数を定義することで疑似的にローカル関数を定義するハックを利用しています。
-
Unified Function Call Syntax の略。
func(arg1, arg2, arg3)
をarg1->func(arg2, arg3)
のように、関数の第一引数のメソッドであるかのように関数呼び出しを書けます。これによってメソッドチェーンの如く数珠繋ぎにフローを書くことができ、可読性が高まります。 Dlang での名称。 Wikipedia(en) には記事がありますが、その他の言語では UFCS が正式な名称ではなさそうなので「っぽいもの」としました。↩ - このことは Vim のマニュアルにも書かれています。↩